Actions and inputs
Pricing toggle
A billing period switch where the thumb and the price move together, so it is clear that one choice changed the price.
Live preview
Loading preview
A Monthly and Yearly switch above a price card that shows $24 per month or $19 per month billed yearly. Replay switches the period twice.
Installation
Run this in your app after the one-time setup on the Getting started page.
$ npx @ganisigit/lunia-ds add pricing-toggleUsage
The demo above, as code.
demo.tsx
'use client';
import * as React from 'react';
import { PricingToggle } from './pricing-toggle';
const PLANS = {
monthly: { amount: 24, note: 'Billed monthly' },
yearly: { amount: 19, note: '$228 billed yearly' },
} as const;
type Period = keyof typeof PLANS;
export default function PricingToggleDemo({ replay = 0 }: { replay?: number }) {
const [period, setPeriod] = React.useState<Period>('monthly');
React.useEffect(() => {
if (replay === 0) return;
setPeriod('monthly');
const timers = [
window.setTimeout(() => setPeriod('yearly'), 300),
window.setTimeout(() => setPeriod('monthly'), 1600),
];
return () => {
for (const timer of timers) window.clearTimeout(timer);
};
}, [replay]);
return (
<div className="w-full max-w-xs rounded-2xl border border-secondary bg-primary p-6 shadow-xs">
<PricingToggle
data-testid="pricing"
value={period}
onValueChange={(value) => setPeriod(value as Period)}
amount={PLANS[period].amount}
note={PLANS[period].note}
locales="en-US"
/>
</div>
);
}
API
Parts: root (radio group), thumb, option, option label, badge, price (number roll), period text, billing note.
| Prop | Type | Default | Description |
|---|---|---|---|
| value | string | - | Value of the chosen option. |
| options | { value: string; label: string; badge?: string }[] | [monthly, yearly with "Save 20%"] | Options in the switch. |
| label | string | "Billing period" | Accessible name of the radio group. |
| amount | number | - | Price part: amount to show for the chosen period. |
| currency | string | "USD" | Price part: currency code. |
| period | string | "/mo" | Price part: text after the amount. Hidden when the amount is 0. |
| note | string | undefined | Price part: line under the price, for example "$228 billed yearly". |
| Event | Description |
|---|---|
| valueChange | The person chose another option. Payload: the new value. |
Motion
Every row names a token from the Motion page. The last column says what happens when the user asks for reduced motion.
| ID | Trigger | Element | Property | From → to | Timing | Reduced motion |
|---|---|---|---|---|---|---|
| PT1 | Period changes | thumb | translate x | under old option → under new option | duration-4xl (520)ease-throw | Moves at once |
| PT2 | Period changes | thumb | width | old option width → new option width | duration-2xl (420)ease-settle | Resizes at once |
| PT3 | Period changes | option label | text color | text-tertiary → text-primary | duration-lg (320)ease-out | Swaps at once |
| PT4 | Yearly chosen or left | badge | background color, text color | light brand → solid brand | duration-lg (320)ease-out | Swaps at once |
| PT5 | Period changes | price | number roll | $24 → $19 (see number-roll NR2) | duration-6xl (900)ease-out | Swaps at once |
| PT6 | Period changes | billing note | opacity, translate y | 0, 100% → 1, 0 | duration-xl (380)ease-settle | Shows at once |
Accessibility
- role="radiogroup" with an accessible name; each option is role="radio" with aria-checked.
- Left and Right arrow keys move the choice; only the chosen option is in the tab order.
- The thumb is decorative and aria-hidden.
- The price exposes its final value to screen readers (see number-roll).