Skip to content

Slider

A slider primitive for numeric input with single and range (two-thumb) modes, keyboard stepping, and customizable thumb alignment.

Updated View as Markdown

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/PageDown and Shift+Arrow move by largeStep. Home jumps to min, End jumps to max.
  • Thumb alignment: The thumbAlignment option 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, and step.
  • Range mode: When two thumbs are present (or defaultValue is 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-range element receives inline width (horizontal) or height (vertical) plus positional styles to render the filled region between the thumbs or from the track start to a single thumb.
Navigation

Type to search…

↑↓ navigate↵ selectEsc close