Components · Forms & selection
Range
A slider for a value or a span. A muted track fills Primary up to a primary thumb with a canvas ring; the dual-thumb mode selects a range with a primary band between the handles. Optional ticks, a live value, and a label round it out.
Preview
Volume60
Price range$20–$70
Build with the skill
No package to install — hand this to your AI to generate or migrate the component in your stack.
Apply the Zetta "Range" component to this project using the zetta-design-md skill.
WHEN TO USE
- Migrating or fixing existing code (the common case): audit the current implementation against the `range` spec (variants: single, dual, ticks, disabled) and CORRECT every deviation — replace off-spec values, don't only add new ones.
- New component: build it to the `range` spec in this project's stack.
AUDIT & FIX (when the component already exists)
Compare it to the skill's spec and fix, don't just append: hardcoded colors/hex, off-scale radius, wrong fonts, dark/navy fills, shadows used in place of borders (or missing borders), brand lime used as a CTA or interaction (must be primary indigo), and any missing or wrong hover / focus / active / disabled / invalid state or missing 2px focus ring. Replace each with the correct Zetta token.
ALWAYS
- Pull the authoritative spec and tokens from the skill — never guess or hardcode values.
- Use this project's own component conventions and framework idioms.
- Honor the Zetta guardrails (brand = identity only, primary = interaction, shadows for overlays) and verify in Light, Dark, and Accessibility. Anatomy & rules
Label 42
╭────────────────────────────────╮
│ ──────────●────────────────── │
│ 0 100│
╰────────────────────────────────╯
↑ range-fill ↑ range-thumb
Parts:
range-label— form label above the trackrange-track— full-width background barrange-fill— primary-colored fill from min to current valuerange-thumb— draggable circle indicatorrange-value— live numeric readout (optional, right-aligned beside label)range-helper— helper text below trackrange-tick+range-tick-label— optional step markers below track
| State | Track fill | Thumb | Notes |
|---|---|---|---|
| Default | {colors.primary} to current value |
{colors.surface-card} + border |
— |
| Hover | Unchanged | Slightly larger scale | Thumb scale 1.1 |
| Focus | Unchanged | 2px solid {colors.border-focus} outline |
Keyboard only |
| Active (dragging) | Unchanged | {colors.primary} fill |
Mouse down on thumb |
| Disabled | {colors.disabled-bg} fill |
{colors.disabled-bg} |
not-allowed |
When to use
- Selecting approximate values where an exact number is not critical: brightness, volume, opacity, zoom level, price range endpoint.
- Values on a continuous scale where the user benefits from visual feedback of the relative position.
When not to use
- Exact numeric entry — use Text Field with number input instead.
- Selections from a small, discrete set (< 5 options) — use Radio.
- Step values that are non-linear (e.g. 1, 5, 10, 50, 100) — use Select instead.
Do
- Always show the current value when precision matters. Use
showValueor display it beside the label. - Set meaningful
minandmax— avoid 0–100 as a default when the real domain is narrower. - Use
stepto constrain to meaningful increments. A price slider in $100 steps is better than $1 steps.
Don't
- Don't use for ranges with more than ~200 steps — the thumb becomes too small to click precisely.
- Don't use a slider for data entry that will be submitted in a form requiring an exact value.
- Don't hide the value — users need to know what they've set.
- Label: noun phrase describing what is being set. "Zoom level", "Maximum price", "Font size".
- Value display: show the unit inline — "72px", "$450", "80%".
- Tick labels: show min and max values only unless the range has 5 or fewer meaningful stops.
Keyboard
| Key | Behavior |
|---|---|
Tab |
Move focus to the slider thumb |
Left / Down Arrow |
Decrease value by one step |
Right / Up Arrow |
Increase value by one step |
Page Down |
Decrease value by a large step (10×) |
Page Up |
Increase value by a large step (10×) |
Home |
Set to minimum value |
End |
Set to maximum value |
Accessibility
- ARIA role:
role="slider"witharia-valuemin,aria-valuemax,aria-valuenow, andaria-valuetext(for units: "72 pixels", "$450"). - Label association: connect via
aria-labelledbypointing to the label element. - Disabled:
aria-disabled="true"anddisabledattribute. - Touch target: the thumb must be at minimum 44×44px via padding or hit area expansion — the visual 16px thumb size is below threshold.
- Value announcement: use
aria-valuetextto provide a human-readable value with units. Screen readers readaria-valuenowas a raw number without context.
Spec
Values and token references resolved straight from the Zetta spec.
range
- width
- 100%
- cursor
- pointer
range-track
- height
- 4px
- borderRadius
- {rounded.full}
- backgroundColor
- {colors.surface-muted}
- overflow
- hidden
range-fill
- height
- 100%
- backgroundColor
- {colors.primary}
- borderRadius
- {rounded.full}
range-thumb
- width
- 16px
- height
- 16px
- borderRadius
- {rounded.full}
- backgroundColor
- {colors.primary}
- border
- 2px solid {colors.canvas}
- boxShadow
- {elevation.shadow.raised}
- cursor
- pointer
range-thumb-hover
- backgroundColor
- {colors.primary-hover}
- transform
- scale(1.15)
range-thumb-active
- transform
- scale(1.1)
- backgroundColor
- {colors.primary-active}
range-focus
- outline
- 2px solid {colors.border-focus}
- outlineOffset
- 4px
range-disabled
- cursor
- not-allowed
- opacity
- 0.5
range-disabled-track
- backgroundColor
- {colors.disabled-bg}
range-disabled-thumb
- backgroundColor
- {colors.disabled-border}
- cursor
- not-allowed
range-label
- fontFamily
- Geist
- fontSize
- 13px
- fontWeight
- 500
- textColor
- {colors.ink}
- lineHeight
- 1.40
range-helper
- fontFamily
- Geist
- fontSize
- 12px
- fontWeight
- 400
- textColor
- {colors.muted}
- marginTop
- {spacing.xs}
range-value
- fontFamily
- Geist
- fontSize
- 13px
- fontWeight
- 500
- textColor
- {colors.ink}
- minWidth
- 32px
- textAlign
- right
range-tick
- width
- 2px
- height
- 6px
- backgroundColor
- {colors.hairline}
- borderRadius
- {rounded.full}
- position
- absolute
- bottom
- -10px
- transform
- translateX(-50%)
range-tick-label
- fontFamily
- Geist
- fontSize
- 11px
- fontWeight
- 400
- textColor
- {colors.muted}
- position
- absolute
- bottom
- -24px
- transform
- translateX(-50%)
range-wrapper
- display
- flex
- flexDirection
- column
- gap
- {spacing.xs}
range-thumb-min
- zIndex
- 2
range-thumb-max
- zIndex
- 2
range-thumb-overlap
- zIndex
- 3
range-fill-between
- height
- 100%
- backgroundColor
- {colors.primary}
- position
- absolute
range-fill-outside
- backgroundColor
- {colors.surface-muted}
range-vertical
- width
- 6px
- height
- 200px
- cursor
- ns-resize
- writing-mode
- vertical-lr
range-vertical-track
- width
- 4px
- height
- 100%
range-vertical-fill
- width
- 100%
- bottom
- 0
- backgroundColor
- {colors.primary}
Known gaps
- Custom thumb shape (non-circular) — not yet specified.
- Slider value tooltip (floating label above thumb on drag) — not yet specified.