Odometer stepper
Number stepper where every digit is its own rolling wheel; supports press-and-hold, sideways drag, the scroll wheel and the keyboard.
Playground
Change a prop and the component re-renders. Props marked remounts set an initial value, so the component starts over.
<UiOdometer
defaultValue={8192}
min={1024}
max={65536}
step={1024}
label="Max output tokens"
/>Installation
pnpm dlx shadcn@latest add @lumesec/ui-odometerFirst timeRegister the @lumesec registry once, or install by URL+
Adds @lumesec to your components.json:
pnpm dlx shadcn@latest registry add @lumesec=https://elements.lumesec.ai/r/{name}.jsonOr skip the setup and install by URL:
npx shadcn@latest add https://elements.lumesec.ai/r/ui-odometer.jsonUsage
import { UiOdometer } from "@/components/lumesec/ui-odometer";
export function Example() {
return <UiOdometer defaultValue={8192} min={1024} max={65536} step={1024} label="Max output tokens" />;
}Behaviour
Every digit is its own wheel, and changes ripple from the right like a mechanical counter. Columns and the thousands separator slide in when the number gets longer.
API reference
Props
Also accepts every prop of <div> (React.ComponentProps<"div">), spread onto the root element.
valueTypenumberControlled value, rounded to the nearest multiple of step and clamped to min/max. Use with onValueChange.
defaultValueTypenumberDefault8192Initial value when uncontrolled.
minTypenumberDefault1024Lowest value.
maxTypenumberDefault65536Highest value. Also sets the number of digit wheels.
stepTypenumberDefault1024Increment per step.
labelTypestringDefault"Max output tokens"Caption above the number and the spinbutton's aria-label.
unitTypestringOptional monospace caption under the number; hidden when empty.
onValueChangeType(value: number) => voidCalled on every value change from the buttons, drag, wheel or keys.
onValueCommitType(value: number) => voidCalled with each change from the buttons, wheel or keys, and once when a drag on the number ends, even if the value did not change.
Ref
ref points at the root HTMLDivElement.
Keyboard
| Keys | Action |
|---|---|
| ArrowUp / ArrowRight | Increase by one step. |
| ArrowDown / ArrowLeft | Decrease by one step. |
| PageUp / PageDown | Change by ten steps, stopping at max or min. |
| Home / End | Jump to min / max. |
Accessibility
- The number has
role="spinbutton",tabindex="0",aria-valuenow,aria-valuemin,aria-valuemaxand anaria-labelfromlabel. - The minus and plus buttons are native buttons labelled "Decrease" and "Increase". They get
aria-disabled="true"at the limits but stay clickable. unitis not part of the spinbutton's accessible value.- The digit wheels are
aria-hidden; the spinbutton'saria-valuetextcarries the number as shown, for example "8,192". - Reduced motion: the digit wheels, column widths and separators change without transitions, and pushing past a limit does not shake.
Theming
Styled with Tailwind classes on your shadcn theme tokens, so light and dark follow your theme. The accent comes from --lumesec. See Theming.
This component reads --foreground, --muted-foreground, --card, --muted, --border and --lumesec.
Notes
- Holding a button repeats after 380 ms and speeds up; after 14 repeats it moves four steps at a time.
- Dragging sideways on the number changes it by one step per 12 px. The scroll wheel over the number changes one step per tick and does not scroll the page; sideways scrolling is ignored.
- Digits are grouped with a comma every three places. The display is built for non-negative whole numbers.
- Changing
valuefrom outside rolls the wheels without calling either callback.
Related
Was this page helpful?