---
title: Slider
description: "A slider primitive for numeric input with single and range (two-thumb) modes, keyboard stepping, and customizable thumb alignment."
order: 18
tags: [primitives]
---

# 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.

```ts
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

```ts
import { Slider } from "@areia/slots";
```

## Usage

```ts
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.

```html
<!-- 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:

```ts
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.

```ts
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.
