Lunia DS

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.

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-toggle

Usage

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.

PropTypeDefaultDescription
valuestring-Value of the chosen option.
options{ value: string; label: string; badge?: string }[][monthly, yearly with "Save 20%"]Options in the switch.
labelstring"Billing period"Accessible name of the radio group.
amountnumber-Price part: amount to show for the chosen period.
currencystring"USD"Price part: currency code.
periodstring"/mo"Price part: text after the amount. Hidden when the amount is 0.
notestringundefinedPrice part: line under the price, for example "$228 billed yearly".
EventDescription
valueChangeThe 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.

IDTriggerElementPropertyFrom → toTimingReduced motion
PT1Period changesthumbtranslate xunder old option → under new optionduration-4xl (520)ease-throwMoves at once
PT2Period changesthumbwidthold option width → new option widthduration-2xl (420)ease-settleResizes at once
PT3Period changesoption labeltext colortext-tertiary → text-primaryduration-lg (320)ease-outSwaps at once
PT4Yearly chosen or leftbadgebackground color, text colorlight brand → solid brandduration-lg (320)ease-outSwaps at once
PT5Period changespricenumber roll$24 → $19 (see number-roll NR2)duration-6xl (900)ease-outSwaps at once
PT6Period changesbilling noteopacity, translate y0, 100% → 1, 0duration-xl (380)ease-settleShows 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).