Components
Unit field
A number field with its unit beside it. Switching the unit converts the amount and the figure counts across to the new value rather than jumping, so it is clear the quantity is the same.
pnpm dlx shadcn@latest add @caliper/unit-fieldWhen to use
Use it for
- Weights, lengths, temperatures and distances people think of in different units.
- Settings and forms used across regions.
Skip it when
- Currencies; rates change, so use `MoneyField` with an explicit conversion.
- Units that are not a straight line from one another, such as shoe sizes.
Decisions
Why it is built this way, so you know what to keep when you change your copy.
Behaviour
- One base value
- The amount is kept in a base unit and each unit is a straight line from it, so switching back and forth never drifts.
- Counts across
- The figure travels from the old number to the new over four tenths of a second, which says "same amount, new unit".
Usage
- Unit in the name
- The unit is read with the field, and the switch is a segmented control named after the field.
Installation
pnpm dlx shadcn@latest add @caliper/unit-field
Adds its 3 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.
Usage
import { type ReactNode } from "react" import { UnitField, type FieldUnit } from "@/components/ui/unit-field" function Panel({ title, context, className = "max-w-md", children }: { title: string; context: string; className?: string; children: ReactNode }) { return ( <section className={`grid w-full gap-5 rounded-overlay border border-border bg-background p-6 shadow-resting max-sm:p-4 ${className}`}> <div className="grid gap-1"> <p className="font-medium">{title}</p> <p className="text-sm text-muted-foreground">{context}</p> </div> {children} </section> ); } const weights: FieldUnit[] = [ { value: "kg", label: "kg", factor: 1, decimals: 1 }, { value: "lb", label: "lb", factor: 0.45359237, decimals: 1 }, ]; const temperatures: FieldUnit[] = [ { value: "c", label: "°C", factor: 1, decimals: 1 }, { value: "f", label: "°F", factor: 5 / 9, offset: -32 * (5 / 9), decimals: 0 }, ]; export function UnitFieldDemo() { return ( <Panel title="Shipment" context="Switch units; the amount stays the same."> <UnitField label="Parcel weight" units={weights} defaultValue={12.5} /> <UnitField label="Keep below" units={temperatures} defaultValue={8} /> </Panel> ); }
API reference
UnitField
component
Also accepts the native props of its root element and a ref.
toBase
component
No props of its own; it accepts the native props of its root element.
fromBase
component
No props of its own; it accepts the native props of its root element.
Accessibility
It is built on native elements and keeps aria-describedby in step with state.
No custom key handling; Tab and the native activation keys do the work.
Motion
It checks prefers-reduced-motion itself and settles immediately instead of travelling. Every state is still reachable.