Slider
A primitive for building slider inputs. Supports single-value and range (two-thumb) modes, keyboard navigation with configurable step sizes, CSS-driven visual range fill, and thumb alignment strategies for different visual systems.
import { Slider } from "@areia/slots";
document.querySelector("#root")!.innerHTML = `
<div data-slot="slider" class="relative h-8 w-72">
<div data-slot="slider-track" class="win95-inset absolute top-1/2 h-2 w-full -translate-y-1/2 bg-white">
<div data-slot="slider-range" class="h-full bg-blue-900"></div>
</div>
<div data-slot="slider-thumb" class="size-5 border-2 border-t-white border-l-white border-r-black border-b-black bg-[#c0c0c0]"></div>
</div>
`;
const root = document.querySelector('[data-slot="slider"]')!;
const controller = Slider.createSlider(root, {
defaultValue: 50,
min: 0,
max: 100,
step: 1,
});
Import
import { Slider } from "@areia/slots";
Usage
import { Slider } from "@areia/slots";
// Auto-bind a single root
const root = document.querySelector('[data-slot="slider"]');
const controller = Slider.createSlider(root, {
defaultValue: 50,
min: 0,
max: 100,
step: 1,
largeStep: 10,
onValueChange: (value) => console.log("value:", value),
onValueCommit: (value) => console.log("committed:", value),
});
// Auto-bind all unbound roots in scope
const controllers = Slider.create();
Expected Markup
The slider requires a track and one or two thumbs inside the root. The root (or an explicit slider-control wrapper) should be positioned so the absolutely positioned thumb is constrained to the track width. An optional range element visualizes the filled portion between the start and the thumb(s). For range mode, use two thumbs.
<!-- Single value -->
<div data-slot="slider" style="position: relative">
<div data-slot="slider-track">
<div data-slot="slider-range"></div>
</div>
<div data-slot="slider-thumb"></div>
</div>
<!-- Range (two-value) -->
<div data-slot="slider" style="position: relative">
<div data-slot="slider-track">
<div data-slot="slider-range"></div>
</div>
<div data-slot="slider-thumb"></div>
<div data-slot="slider-thumb"></div>
</div>
Range mode is indicated by providing a comma-separated defaultValue (e.g. "25,75") or a two-element array [25, 75].
Data Slots
| Slot | Required | Description |
|---|---|---|
slider | Yes | Root container. Supports data-disabled. |
slider-track | Yes | The track along which the thumb(s) slide. |
slider-range | No | Visual element showing the filled range. Width/height set via inline style. |
slider-thumb | Yes | Draggable thumb handle. Use one for single, two for range mode. |
Generated Attributes
The controller writes the following attributes on initialization and updates them as state changes:
| Attribute | Element(s) | Description |
|---|---|---|
data-orientation | Root, control, track, range, thumb | "horizontal" or "vertical". |
data-disabled | Root, control, track, range, thumb | Present when the slider is disabled. |
data-dragging | Root, active thumb | Present while a thumb is being actively dragged. |
data-index | slider-thumb | Zero-based index of the thumb in range mode. |
aria-valuemin | slider-thumb | Minimum value for this thumb. |
aria-valuemax | slider-thumb | Maximum value for this thumb. |
aria-valuenow | slider-thumb | Current value of this thumb. |
aria-orientation | slider-thumb | "horizontal" or "vertical". |
Range Element
The slider-range element receives inline width/height and positional styles (e.g. left, right, top, bottom) to visually indicate the filled portion of the track. For a single thumb, the range spans from the track start to the thumb. For range mode, it spans between the two thumbs.
Events
All events are dispatched from the root element (data-slot="slider").
Outbound
| Event | Detail | Description |
|---|---|---|
slider:change | { value: number | [number, number] } | Fired on every value change during drag, keyboard, or programmatic update. |
slider:commit | { value: number | [number, number] } | Fired when the user finishes interacting (drag end, key release). |
Inbound
| Event | Detail | Description |
|---|---|---|
slider:set | { value: number | [number, number] } | Dispatch on the root to programmatically set the value. |
Send slider:set on the root element to update the value without a controller reference:
const root = document.querySelector('[data-slot="slider"]');
root?.dispatchEvent(
new CustomEvent("slider:set", {
bubbles: true,
detail: { value: 75 },
}),
);
API Reference
Options
Slider.createSlider(root, options)
| Option | Type | Default | Description |
|---|---|---|---|
defaultValue | number | [number, number] | — | Initial value. A two-element array or comma-separated string (e.g. "25,75") enables range mode. |
min | number | 0 | Minimum allowed value. |
max | number | 100 | Maximum allowed value. |
step | number | 1 | Step increment for arrow keys and dragging. |
largeStep | number | 10 | Step increment for PageUp/PageDown and Shift+arrow keys. |
orientation | "horizontal" | "vertical" | "horizontal" | Layout direction. Controls movement axis and ARIA attributes. |
thumbAlignment | "center" | "edge" | "edge-client-only" | "center" | How the thumb aligns to the track value. See behavior section for details. |
disabled | boolean | false | Disable the entire slider. |
onValueChange | (value: number | [number, number]) => void | — | Called on every value change. |
onValueCommit | (value: number | [number, number]) => void | — | Called when the user finishes interacting (drag end, key release). |
Controller
| Member | Type | Description |
|---|---|---|
value | number | [number, number] (getter) | Current value. Range mode returns a two-element array. |
min | number (getter) | Minimum value. |
max | number (getter) | Maximum value. |
disabled | boolean (getter) | Whether the slider is disabled. |
setValue(value) | (value: number | [number, number]) => void | Set the value programmatically. |
destroy() | () => void | Remove all event listeners and clean up. |
Controller
The controller is returned by Slider.createSlider() and provides imperative control over the slider's value.
const controller = Slider.createSlider(root, {
defaultValue: 50,
min: 0,
max: 100,
step: 1,
});
// Read current value
console.log(controller.value); // 50 (single) or [25, 75] (range)
// Read constraints
console.log(controller.min); // 0
console.log(controller.max); // 100
console.log(controller.disabled); // false
// Set value programmatically
controller.setValue(75);
// In range mode:
controller.setValue([30, 70]);
// Tear down
controller.destroy();
Behavior
- Keyboard stepping: Arrow keys move the thumb by
step.PageUp/PageDownandShift+Arrowmove bylargeStep.Homejumps tomin,Endjumps tomax. - Thumb alignment: The
thumbAlignmentoption controls how the thumb maps to the track value:"center"(default): Positions the thumb at the raw percentage and translates it by half its size, so the thumb center sits on the value."edge": Insets the travel area by the thumb size, keeping the thumb fully inside the track at the min/max edges."edge-client-only": Alias for"edge"for Base UI compatibility.
- Drag: Thumbs can be dragged with mouse or touch along the track, constrained by
min,max, andstep. - Range mode: When two thumbs are present (or
defaultValueis a two-element array / comma-separated string), the slider operates in range mode. The two thumbs are independently draggable and never cross each other. - Range fill: The
slider-rangeelement receives inlinewidth(horizontal) orheight(vertical) plus positional styles to render the filled region between the thumbs or from the track start to a single thumb.