Components
Ruler slider
A tape of ticks that slides under a fixed needle. Drag it and let go: it keeps the speed you gave it, slows, and clicks into the nearest step while the number above rolls to the new value. For picking a measurement where the scale itself helps.
pnpm dlx shadcn@latest add @caliper/ruler-sliderWhen to use
Use it for
- Physical quantities with a familiar scale: weight, height, distance, temperature.
- Touch-first settings where a wide sweep beats a small thumb.
Skip it when
- Exact numbers people already know; a `NumberField` is faster to type into.
- Ranges or two values at once; use `Slider`.
Decisions
Why it is built this way, so you know what to keep when you change your copy.
Behaviour
- The tape moves, not the needle
- The value is always under the same point, so the eye stays put and the scale passes beneath it, as on a real tape.
- Momentum, then a detent
- A flick carries the tape on with friction; the value commits at each step as it passes, and the needle gives a small click when it lands.
- Ends you can feel
- Dragging past the limits stretches the tape a little and springs back, instead of stopping dead.
Usage
- A slider underneath
- It is a slider to assistive technology: arrows step, Page keys jump a numbered tick, and the value is read with its unit.
Installation
pnpm dlx shadcn@latest add @caliper/ruler-slider
Adds its 4 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
This is the code behind the preview above.
import { useState, type ReactNode } from "react" import { RulerSlider } from "@/components/ui/ruler-slider" 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> ); } export function RulerSliderDemo() { const [weight, setWeight] = useState(68.5); return ( <Panel title="Target weight" context="You can change this later in Health."> <RulerSlider label="Target weight" unit="kg" min={40} max={140} step={0.5} majorEvery={10} value={weight} onValueChange={setWeight} /> <p className="text-center text-sm text-muted-foreground">Drag the ruler, or use the arrow keys.</p> </Panel> ); }
API reference
RulerSlider
component
Also accepts the native props of its root element and a ref.
Accessibility
It exposes slider semantics, with aria-label, aria-valuemin, aria-valuemax, aria-valuenow, aria-valuetext and aria-hidden kept in step with state.
Motion
It checks prefers-reduced-motion itself and settles immediately instead of travelling. Every state is still reachable.
Related
- SliderA native range input styled to match, with accessible value text.
- Scrub fieldA number you change by dragging its label, with a ruler, detents and momentum.
- Rotary knobA rotary control with a real edge. The knob is a short cylinder seen from above: a stack of discs gives it thickness, a ring of ticks lights up to the value, and the number is always written beside it. Drag it or use the keys; with detents on it clicks through every tenth.