Skip to content
txbrendan/ui

Unit Switch

beta

The unit beside a value, like £, min, or km, that you can change in place. Click it for a menu of units. On a change the unit crossfades to the new one, and a note can say what changed.

Preview

currency

Ticket price6.00

stepper

size

custom

Install

  1. 1. Add the registry to components.json, once per app. Without it, the shadcn CLI looks up @txbrendan in a public list that txbrendan/ui does not control.

    npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"
  2. 2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.

    npx shadcn@latest add @txbrendan/theme
  3. 3. Add the component.

    npx shadcn@latest add @txbrendan/unit-switch

Use it for

  • A value whose unit people may want to change: a fee in pounds or dollars, a length in minutes or hours, a distance in km or miles.
  • Pass a set name as units: "currency", "duration", "distance", or "weight". Or pass your own list of ids and options.
  • A currency's id is its code, like "GBP", so you can pass it straight to Intl.NumberFormat.
  • Put it in a NumberStepper's suffix for a count with a unit. Beside plain text, put it before or after the value, as its place says.
  • Give announce to say what changed, like { text: "Pounds changed to dollars", key: 3 }. Change the key to say it again.
  • matchUnit reads a typed value, like "2 hours", and returns the unit in it. Use it to switch the unit when someone types one.

Avoid it for

  • A unit that never changes. Write it as text, or use NumberStepper's suffix.
  • Picking anything that is not a unit. Use a select or a menu.

Props

  • units

    A built-in set, or your own list. A string in a list is a built-in id, like "mins", or a plain unit.

    "currency" | "duration" | "distance" | "weight" | Array<string | UnitOption> · default "currency"

  • value

    The unit's id. Use it with onValueChange.

    string

  • defaultValue

    The unit at first render. Defaults to the first one.

    string

  • onValueChange

    Called with the new unit's id when someone picks one.

    (id: string) => void

  • announce

    Shows a note above the unit for a moment, and says it to screen readers.

    { text: string; key: number | string } | null

  • size

    Text size and padding, to match the field it sits in.

    "sm" | "md" | "lg" · default "md"

  • disabled

    Blocks the menu.

    boolean · default false

  • aria-label

    Names the button. Defaults to the unit's name, like "Pounds". A unit that shows letters puts them first, like "min, Minutes".

    string

  • title

    The tooltip. Defaults to "Change currency", or "Change unit".

    string

  • className

    Classes for the button. Later classes win.

    string

Accessibility

  • The unit is a menu button: Enter, Space, or the down arrow opens the menu, and focus starts on the current unit.
  • With a mouse it opens on the press. Let go on a unit to pick it, or click to leave the menu open.
  • The button's name is the unit's name, like "Pounds", and its tooltip, "Change currency", describes it.
  • A unit that shows letters puts them first in its name, like "min, Minutes", so people who speak commands can say what they see.
  • Each unit is a radio item named by its name, so screen readers hear which one is checked.
  • Picking a unit closes the menu and puts focus back on the button.
  • The note is said once through a polite live region. The visible copy is hidden from screen readers, so it is not read twice.
  • The label uses the shared MotionSwap fade and layout tokens. Motion off changes it immediately; reduced motion keeps only the fade.

Examples

Currency beside a price

<p className="flex items-center gap-1">
  <UnitSwitch units="currency" defaultValue="GBP" />
  <span>6.00</span>
</p>

Duration in a stepper

<NumberStepper
  aria-label="Meeting length"
  defaultValue={90}
  suffix={<UnitSwitch units="duration" defaultValue="mins" />}
/>

Switch it from typed text

const unit = matchUnit("2 hours", "duration"); // { id: "hours", ... }