# Billing Toggle (Lumen Halo): prompt.md (v1.0.0)

- id: `lumen-billing-toggle` · version 1.0.0 · component · pro (Pro)
- category: Actions
- build: Base UI (one set of files; its dependencies follow the build)
- install (this build): `npx shadcn@latest add @beautiful-ui-pro/lumen-billing-toggle`
- npm dependencies: none
- registry dependencies: utils, https://beautiful-ui.dev/r/lumen-foundation.json
- docs: https://beautiful-ui.dev/components/lumen-billing-toggle
- The install command carries everything this item needs (files, CSS, tokens, npm and registry dependencies). Prefer it to copying source by hand.

The monthly / yearly choice on a pricing page, made to feel like a reward: a lit thumb springs across, the savings chip charges up cell by cell, and Billing Price rolls each digit to the new amount like an odometer, striking through what you no longer pay.

## Build it
- Stack: React 19 (`ref` is a plain prop), TypeScript, Tailwind CSS v4 utilities, a shadcn-initialised project with the `@/*` alias.
- Packages: none beyond React.
- Files: `components/ui/lumen/controls/billing-toggle.tsx`.
- Registry dependencies, installed with it automatically: shadcn `utils` (cn), `lumen-foundation`.
- Builds: one set of files for both, but its dependencies come in Base UI and Radix builds. Install the one that matches the project (see Install): a free item's bare URL installs the Base UI build of it and its dependencies.
- Exports to keep: `BillingToggle`, `BillingPrice`, and every exported type.
- CSS: the install merges this item's rules (the registry `css` field) into your global stylesheet, in `@layer components`, and adds the lumen foundation (tokens, keyframes, motion levels) once. Nothing to import by hand.
- Re-running `add` (or `--overwrite`) re-applies those rules: put overrides in your own CSS, never in the installed rules.
- Tokens: retheme with the `--lumen-*` custom properties (`--lumen-accent`, `--lumen-accent-foreground`, `--lumen-accent-text`, `--lumen-bad`, `--lumen-bad-text`, `--lumen-chart-accent`, `--lumen-chart-accent-foreground`, `--lumen-chart-accent-text`, `--lumen-focus`, `--lumen-good`, `--lumen-good-text`, `--lumen-hairline`, `--lumen-ink`, `--lumen-muted-ink`, `--lumen-series-1`, `--lumen-series-2`, `--lumen-series-3`, `--lumen-series-4`, `--lumen-series-5`, `--lumen-series-6`, `--lumen-warn`, `--lumen-warn-text`); this item's CSS also reads `--lumen-font-sans`, `--lumen-radius-k`, `--lumen-roll-delay`. Never add Tailwind colour classes inside the component.

```tsx
import { BillingPrice, BillingToggle, type BillingPeriod } from "@/components/ui/lumen/controls/billing-toggle";
```

## Parts

| Part | data-slot | What it is for |
|---|---|---|
| `BillingToggle` | — | Monthly or yearly. |
| `BillingPrice` | — | The rolling price and its note. |

## Sound
- Keep every `data-slot` and `data-sound` attribute: the sound layer reads them.
- Installing this item adds no audio. Nothing plays until the app mounts `GlassSoundProvider` once (install: `npx shadcn@latest add https://beautiful-ui.dev/r/glass-sound.json`, import from `@/components/beautiful-ui/glass-sound`); `GlassSoundToggle` is its mute control. Without a provider the audio engine never loads.

## Match the original
- Read `components/ui/lumen/controls/billing-toggle.tsx` as the reference implementation before changing or recreating anything, and match it: sizes, colours per theme, motion timings, copy and behaviour.
- If you deviate (a prop you can't honour, a style you changed, a dependency you swapped), say so in your reply, part by part.
- Keep the accessibility contract, the keyboard map and the motion levels listed below.

## Use it when
- billing toggle, monthly yearly switch, annual pricing toggle, pricing page, plan period, save 20%, Lumen
- Pricing pages and upgrade dialogs with monthly and yearly prices

### Not when
- More than two periods: use Toggle Group

## Mistakes
- Pass the per-month price for both periods; the note works out the yearly total

## Usage

```tsx
"use client";

import * as React from "react";
import { BillingPrice, BillingToggle, type BillingPeriod } from "@/components/ui/lumen/controls/billing-toggle";

export function PlanPrice() {
  const [period, setPeriod] = React.useState<BillingPeriod>("yearly");
  return (
    <div className="grid gap-4">
      <BillingToggle value={period} onValueChange={setPeriod} savings="Save 20%" />
      <BillingPrice period={period} monthly={24} yearly={19} />
    </div>
  );
}
```

## Props

| Prop | Type | Default | What it does |
|---|---|---|---|
| `BillingToggle value / defaultValue / onValueChange` | `"monthly" \| "yearly"` | `"monthly"` | The period, controlled or not. |
| `BillingToggle savings` | `string` |  | The chip beside Yearly, such as "Save 20%"; its cells light when Yearly is chosen. |
| `BillingToggle labels` | `{ monthly?, yearly? }` |  | The words, for other languages. |
| `BillingPrice period / monthly / yearly` | `period, number, number` |  | The price per month on each plan; the digits roll between them. |
| `BillingPrice currency / locale` | `string` | `"USD"` | Formatting (Intl). |
| `BillingPrice per / note` | `string / ReactNode` | `"/ month" / "Billed yearly, $228"` | After the amount, and the line under it. |

Full docs: https://beautiful-ui.dev/components/lumen-billing-toggle

## Customising
- Colours: set the `--lumen-*` tokens on `:root`, or on a container with the `lumen-scope` class to retheme one area. Add the `lumen-inherit` class to follow your shadcn palette instead (`--chart-N`, `--destructive`).
- Dark mode follows the `.dark` class on an ancestor (the shadcn and next-themes convention).
- Update later by re-running the install with `--overwrite` (review the diff if you edited it). Changelog: https://beautiful-ui.dev/r/changelog.json

## Keyboard

| Keys | Action |
|---|---|
| Tab | To the chosen period |
| Arrow keys | Switch period |

## Motion inventory

| Interaction | What moves |
|---|---|
| Switch | The thumb springs across; the savings cells light one by one (70ms apart) |
| Price | Each digit rolls on its own strip, staggered from the right; the old price slides in, struck |

## Accessibility contract (preserve when editing)
- A radio group: one tab stop, arrow keys switch
- The price is read as one phrase (the rolling digits are hidden from screen readers)
- The struck price is read as "instead of $24"

## Install

```bash
npx shadcn@latest add @beautiful-ui-pro/lumen-billing-toggle
```

Pro item: needs the `@beautiful-ui-pro` registry in `components.json` and `BEAUTIFUL_UI_TOKEN` in `.env.local` (https://beautiful-ui.dev/account). Setup: https://beautiful-ui.dev/docs/pro. Your components.json `style` picks the build: radix-*, new-york and default get Radix, base-* gets Base UI.

## Credits
- Built on shadcn/ui (https://ui.shadcn.com)

The lumen foundation (the tokens listed above, keyframes and motion levels) installs once with the first component; its CSS is public at https://beautiful-ui.dev/r/lumen-foundation.json.
