Slider
Drag a handle along a rail to pick a number, or two handles to pick a range. Aurora re-exports reka-ui's Slider primitives unchanged so you import them from one place — but there is no Aurora slider component: no variants, no default chrome, no tokens applied for you. Every visible pixel is a class you write. Five call sites across web-app already write the same classes, so copy those rather than inventing a new look.
ts
import { SliderRange, SliderRoot, SliderThumb, SliderTrack } from '@scaler-tech/aurora/slider'
import type { SliderRootEmits, SliderRootProps } from '@scaler-tech/aurora/slider'Single thumb
Target year2035
20252050
Two thumbs
Floor area5,000 – 32,000 m²
0 m²50,000 m²
Composition
SliderRoot owns the value and the geometry; the three children are pure presentation.
SliderRoot— takesv-model(an array),min,max,step. Positions the thumbs.SliderTrack— the full-width rail. Needsrelativeso the range can absolutely position inside it.SliderRange— the filled segment from the minimum (or from the lower thumb) up to the value. Must be insideSliderTrack.SliderThumb— one per value in the model array; usev-foronce you have more than one.
The primitives add no props of their own, so reka-ui's Slider docs are the prop reference (orientation, inverted, minStepsBetweenThumbs, disabled, …).
The house styling
This is the markup behind the range in the demo above, and it matches FilterSlider.vue, FilterDataCoverage.vue, CapexScenario.vue, ExitValueSlider.vue, and ForecastDriversCardV3.vue byte for byte. For a single thumb, drop the v-for and pass a one-element array.
vue
<SliderRoot
:model-value="[localMin, localMax]"
:min="bounds.min"
:max="bounds.max"
:step="1"
class="relative flex items-center select-none touch-none w-full h-5"
@update:model-value="handleSliderUpdate"
>
<SliderTrack class="relative bg-disabled grow rounded-full h-1">
<SliderRange class="absolute bg-cta rounded-full h-full" />
</SliderTrack>
<SliderThumb
v-for="i in 2"
:key="i"
class="block w-4 h-4 bg-white border-2 border-cta rounded-full focus:outline-hidden focus:ring-2 focus:ring-focus shadow-100 hover:bg-secondary-hover"
/>
</SliderRoot>Two classes in there are structural rather than cosmetic: relative on SliderTrack is what keeps the absolutely-positioned SliderRange inside the rail instead of escaping to the nearest positioned ancestor, and touch-none on the root stops a drag from scrolling the page on touch devices (select-none stops text selection during a fast drag).
A slider alone tells the user nothing about where the handle landed, so every call site pairs it with a readout — the current value above, and the min/max labels below in paragraph-xs text-secondary tabular-nums.
Gotchas
v-modelis an array even for a single thumb ([50], not50), and@update:model-valuehands backnumber[] | undefined— bind a bare number and the slider silently ignores it. Guard theundefinedand the length before indexing.[external]SliderThumbrenders a bare unstyled element, invisible until you give it size and background —block w-4 h-4 bg-white …is load-bearing, not decoration.[external]- One thumb renders per value. Fewer thumbs than values means a value nobody can drag; more means a dead handle.
[external] - Binding
:model-valueto debounced parent state makes the thumb lag the cursor, since drag fires per pixel. Keep a local ref for the thumb, debounce only the emit outward, andwatchthe prop to sync back.