A styled native range input with a live percentage badge.

Last updated

Uses JavaScript

A small island updates the badge text and the track’s fill on every input event, keeps the badge centred over the thumb, and in range mode stops the two thumbs from crossing.

Without JS: The range input itself needs no JS — it's a native range input, fully draggable and keyboard-operable, and its initial coloured fill is rendered server-side from value/min/max. Only the floating value badge is JS-driven; it's hidden via no-js:hidden when scripts don't run.

Show code
<Slider aria-label="Volume" value={40} />

Add

npx @ava-ui/apogee add slider

No Apogee component dependencies.

Usage

---
import Slider from '@/components/apogee/Slider/Slider.astro'
---
<Slider name="volume" min={0} max={100} value={40} aria-label="Volume" />

API

@composable — exposed to Content Editors in AVA. Any other <input> attribute (e.g. id, aria-labelledby, data-*) passes through onto the range input.

Prop Type Default Description
name string — Form field name.
min number 0 Minimum value.
max number 100 Maximum value.
step number 1 Step increment.
value number midpoint of min/max Initial value. Clamped to min/max and snapped to step before render.
disabled boolean false Disables the input.
range boolean false Two thumbs. Uses minValue / maxValue and submits two fields.
minValue / maxValue number min / max Initial thumb values in range mode.
minName / maxName string <name>-min / <name>-max Submitted names in range mode.
format string | false '{value}' Badge text with a {value} token ('${value}', '{value}%'). false hides the badge.
orientation 'horizontal' | 'vertical' 'horizontal' Vertical turns the input; give the field a height with class.
color 'primary' | 'neutral' | 'critical' | 'positive' | 'warning' 'primary' Hue of the filled track and thumb.
labelPosition 'top' | 'bottom' 'top' Where the badge sits relative to a horizontal track.
aria-label string — Accessible name. No visible <label> is rendered — set this or associate an external <label> via id / aria-labelledby.
class string — Extra classes on the wrapping <div>, merged last.

Value and format

The badge shows the value. format wraps it; false hides the badge.

Show code
<Slider min={0} max={50} value={10} format="${value}" aria-label="Budget" />
<Slider value={40} format="{value}%" aria-label="Opacity" />
<Slider value={25} format={false} aria-label="No badge" />

Range

Two thumbs that never cross; submits price-min and price-max.

Show code
<Slider range name="price" min={0} max={100} step={10} minValue={20} maxValue={60} aria-label="Price" />

With a visible label, aria-labelledby names both thumbs; prefer aria-label when each thumb should be named on its own (“Budget minimum” / “Budget maximum”).

Show code
<label id="budget-label">Budget</label>
<Slider range name="budget" minValue={1000} maxValue={4000} max={5000} step={100} aria-labelledby="budget-label" />

Orientation

Show code
<Slider aria-label="Volume" value={50} />
<Slider orientation="vertical" aria-label="Volume" class="h-48" />

Variants

labelPosition — horizontal only:

Show code
<Slider aria-label="Top badge" labelPosition="top" value={30} />
<Slider aria-label="Bottom badge" labelPosition="bottom" value={70} />

Every color:

Show code
<Slider aria-label="Primary" color="primary" value={60} />
<Slider aria-label="Neutral" color="neutral" value={60} />
<Slider aria-label="Critical" color="critical" value={60} />
<Slider aria-label="Positive" color="positive" value={60} />
<Slider aria-label="Warning" color="warning" value={60} />

States

  • Default — track fill matches value (or the midpoint if value is unset), rendered server-side so there’s no flash before JS loads.
  • Hover — pointer cursor over the track/thumb.
  • Focus — visible ring on the thumb (focus-visible).
  • Disabled — track and thumb take the disabled colour, cursor-not-allowed; native disabled blocks interaction and removes it from the tab order.
  • No JS — badge hidden (no-js:hidden); input and fill stay fully usable.
Show code
<Slider aria-label="Disabled" value={50} disabled />

Examples

With a visible, associated label:

<label for="volume-slider">Volume</label>
<Slider id="volume-slider" name="volume" value={60} />

Controlled range with a fractional step:

<Slider name="rating" min={0} max={5} step={0.5} value={2.5} aria-label="Rating" />

Accessibility

Key Action
Tab Moves focus to the thumb; a range slider has one stop per thumb.
← → Step down / up by step; swapped under dir="rtl".
↓ ↑ Step down / up by step.
PageDown PageUp Step down / up by a tenth of the range, or by step if that is larger.
Home End Jump to min / max.
  • No visible <label> is rendered — always pass aria-label, or associate an external <label for> with id.
  • The badge is aria-hidden — it echoes the input’s value visually; screen readers announce the native input’s value/min/max, not the badge text.
  • Visible focus ring on keyboard focus.
  • No JS: the badge disappears (no-js:hidden) but the slider stays fully operable — value, min, max, and the coloured fill are all rendered server-side.

Tokens

Token Used for
--background-{hue} Filled portion of the track, thumb fill (per color)
--background-neutral Unfilled portion of the track
--background-page Thumb border
--border-neutral Thumb outer shadow ring
--outline-color-focus Focus ring on the thumb
--background-inverted / --on-inverted Badge

Do / Don’t

  • Do pair the slider with a visible value or unit nearby when precision matters — the badge is a nice-to-have echo, not a substitute for feedback.
  • Do set sensible min/max/step for the value being edited (percentages: 0–100 step 1; ratings: 0–5 step 0.5).
  • Don’t rely on the badge alone to communicate the value to screen reader users — it’s decorative.
  • Don’t omit aria-label (or an associated <label>) — without one the slider has no accessible name.

Content

No visible label text is rendered by the component — write aria-label (or an external label) as a short noun phrase (“Volume”, “Price range”), not an instruction (“Drag to adjust volume”).