---
title: Slider
description: A control for choosing a numeric value or range by dragging a thumb.
---

<PackageInfo name="slider" />

**bun**

```bash
bun add @data-slot/slider
```

**npm**

```bash
npm install @data-slot/slider
```

**pnpm**

```bash
pnpm add @data-slot/slider
```

## Anatomy

The thumb count defines the slider: one thumb for a single value, two for a range. `slider-control` is an optional wrapper for when you need another box to style.

```html
<!-- Single value slider -->
<div data-slot="slider" data-default-value="50">
  <div data-slot="slider-track">
    <div data-slot="slider-range"></div>
  </div>
  <div data-slot="slider-thumb"></div>
</div>

<!-- Optional control wrapper -->
<div data-slot="slider" data-default-value="50">
  <div data-slot="slider-control">
    <div data-slot="slider-track">
      <div data-slot="slider-range"></div>
    </div>
    <div data-slot="slider-thumb"></div>
  </div>
</div>

<!-- Range slider (two thumbs) -->
<div data-slot="slider" data-default-value="25,75">
  <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>
```

## Examples

### Drag to set a value

<Example name="slider" />

### Disabled state

`data-disabled` keeps the current value visible but blocks dragging and keyboard changes.

<Example name="slider" variant="extra" />

## API reference

### Initialization

#### `create(scope?)`

Find and bind uninitialized `[data-slot="slider"]` descendants of `scope` (defaults to `document`). Returns `SliderController[]` for newly bound roots. To initialize the scope element itself, use `createSlider`.

```typescript
import { create } from "@data-slot/slider";

const controllers = create();
```

#### `createSlider(root, options?)`

Create a `SliderController` for one root element. JavaScript options take precedence over the corresponding data attributes. Calling this again for a bound root returns its existing controller; destroy it before rebinding with new options.

```typescript
import { createSlider } from "@data-slot/slider";

const controller = createSlider(element, {});
```

### Slots

#### Runtime Slots

- `slider` - Root element that manages the value, bounds, orientation, and disabled state.
- `slider-control` - Optional interaction wrapper around the track and thumbs. When omitted, the track's parent is used, typically the root.
- `slider-track` - Required track used to measure pointer position and lay out the range and thumbs.
- `slider-range` - Optional filled portion of the track; its position and size follow the selected value or range.
- `slider-thumb` - Required draggable control with slider semantics and keyboard support. Use one for a single value or two for a range; two thumbs enable range mode.

### Data Attributes

| Attribute | Description | Default |
|-----------|-------------|---------|
| `data-default-value` | Initial value (`50` or `25,75` for range) | `min` (single), `[min, min]` (range) |
| `data-min` | Minimum value | `0` |
| `data-max` | Maximum value | `100` |
| `data-step` | Step increment | `1` |
| `data-large-step` | Large step for PageUp/PageDown | `step * 10` |
| `data-orientation` | `horizontal` or `vertical` | `horizontal` |
| `data-thumb-alignment` | `center`, `edge`, or `edge-client-only` | `center` |
| `data-disabled` | Disable the slider | - |

### Options

JavaScript options take precedence over root data attributes.

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `defaultValue` | `number \| [number, number]` | `min` (single), `[min, min]` (range) | Initial value; clamped to the bounds and snapped to `step`. Two thumbs enable range mode. |
| `min` | `number` | `0` | Minimum value |
| `max` | `number` | `100` | Maximum value |
| `step` | `number` | `1` | Step increment; non-positive values fall back to `1` |
| `largeStep` | `number` | `step * 10` | Increment for PageUp/PageDown and Shift+Arrow |
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Slider orientation |
| `thumbAlignment` | `"center" \| "edge" \| "edge-client-only"` | `"center"` | Thumb placement at the track edges |
| `disabled` | `boolean` | `false` | Disable user interaction and inbound set events |
| `onValueChange` | `(value: number \| [number, number]) => void` | `undefined` | Called for value changes, including programmatic updates; silent on initialization and unchanged values |
| `onValueCommit` | `(value: number \| [number, number]) => void` | `undefined` | Called on pointer release/cancel, blur after a keyboard value change, or a changed `slider:set` update |

### Controller

| Method/Property | Description |
| --- | --- |
| `setValue(value: number \| [number, number])` | Update the value, including when disabled. Emits change when the value changes, but does not emit commit. |
| `value` | Current value (readonly `number \| [number, number]`) |
| `min` | Resolved minimum (readonly `number`) |
| `max` | Resolved maximum (readonly `number`) |
| `disabled` | Whether interaction is disabled (readonly `boolean`) |
| `destroy()` | Remove listeners and release the root binding |

### Events

#### Outbound Events

Listen for these events on the root element.


| Event | Detail | Description |
|-------|--------|-------------|
| `slider:change` | `{ value: number \| [number, number] }` | Fires during value changes |
| `slider:commit` | `{ value: number \| [number, number] }` | Fires on pointer release/cancel, blur after a keyboard value change, or a changed `slider:set` update |

#### Inbound Events

Dispatch these events on the root element.


| Event | Detail | Description |
|-------|--------|-------------|
| `slider:set` | `{ value: number \| [number, number] }` | Set value programmatically |

```javascript
// Listen for changes
root.addEventListener("slider:change", (e) => {
  console.log("Value:", e.detail.value);
});

// Set value from outside
root.dispatchEvent(new CustomEvent("slider:set", {
  detail: { value: 50 }
}));

// Set range value
root.dispatchEvent(new CustomEvent("slider:set", {
  detail: { value: [25, 75] }
}));
```

**Note:** Blocked when slider is disabled.

#### Deprecated Shapes

The following shapes are deprecated and will be removed in v1.0:

```javascript
// Deprecated: bare number
root.dispatchEvent(new CustomEvent("slider:set", {
  detail: 50
}));

// Deprecated: bare array
root.dispatchEvent(new CustomEvent("slider:set", {
  detail: [25, 75]
}));
```

Use `{ value: ... }` instead.

### Styling

The component sets data attributes and inline styles for CSS hooks:

```css
/* Root state */
[data-slot="slider"][data-orientation="horizontal"] { ... }
[data-slot="slider"][data-orientation="vertical"] { ... }
[data-slot="slider"][data-disabled] { ... }
[data-slot="slider"][data-dragging] { ... }

/* Parts mirror shared state for shadcn/Base-style selectors */
[data-slot="slider-track"][data-orientation="horizontal"] { ... }
[data-slot="slider-track"][data-orientation="vertical"] { ... }
[data-slot="slider-range"][data-orientation="horizontal"] { ... }
[data-slot="slider-range"][data-orientation="vertical"] { ... }
[data-slot="slider-track"][data-disabled] { ... }
[data-slot="slider-range"][data-disabled] { ... }
[data-slot="slider-thumb"][data-orientation="horizontal"] { ... }
[data-slot="slider-thumb"][data-orientation="vertical"] { ... }
[data-slot="slider-thumb"][data-disabled] { ... }

/* Thumb positioning (set automatically) */
[data-slot="slider-thumb"] {
  position: absolute;
  /* --position: X%; inset-inline-start/top or bottom/left are applied inline */
  /* translate is applied inline to center the thumb on the resolved visual position */
}

/* Range thumbs expose their index */
[data-slot="slider-thumb"][data-index="0"] { ... }
[data-slot="slider-thumb"][data-index="1"] { ... }

/* Range positioning (set automatically) */
[data-slot="slider-range"] {
  /* horizontal: relative + inset-inline-start/width */
  /* vertical: absolute + bottom/height */
}

/* Thumb dragging state */
[data-slot="slider-thumb"][data-dragging] { ... }
```

`slider-track`, `slider-range`, and `slider-thumb` also get Base-style inline layout
styles from the controller. The runtime owns the track's `position: relative`, the
range's start/size positioning, and the thumb's absolute position / translate, so
authors only need to supply visual styles unless they intentionally want to override
that layout behavior.

When `thumbAlignment` is `"edge"` or `"edge-client-only"`, the runtime measures the
track and thumb size to keep the thumb inside the visible track. In those modes,
`--position` and the range start/size vars represent rendered layout positions rather
than raw value percentages.

#### Thumb Alignment

By default, slider thumbs are centered on the active value. That means the thumb can
extend beyond the track when the value is at `min` or `max`.

Use `thumbAlignment` or `data-thumb-alignment` to change that behavior:

- `"center"` - Default. The thumb is centered on the value and may overflow the track edges.
- `"edge"` - Insets the thumb so its edge aligns with the track edge at `min` and `max`.
- `"edge-client-only"` - Accepted for Base UI API parity. In this vanilla package it behaves the same as `"edge"`.

```html
<div data-slot="slider" data-default-value="25" data-thumb-alignment="edge">
  <div data-slot="slider-track">
    <div data-slot="slider-range"></div>
  </div>
  <div data-slot="slider-thumb"></div>
</div>
```

```javascript
createSlider(element, {
  defaultValue: 25,
  thumbAlignment: "edge",
});
```

### Keyboard Navigation

| Key | Action |
|-----|--------|
| `ArrowRight` / `ArrowLeft` | Increase / decrease by step in horizontal sliders |
| `ArrowUp` / `ArrowDown` | Increase / decrease by step in vertical sliders |
| `PageUp` | Increase by largeStep |
| `PageDown` | Decrease by largeStep |
| `Home` | Set to min |
| `End` | Set to max |
| `Shift+Arrow` | Move by largeStep |

### Accessibility

Each thumb element receives:
- `role="slider"`
- `tabindex="0"`
- `aria-valuemin` / `aria-valuemax` / `aria-valuenow`
- `aria-orientation`
- `aria-disabled` (when disabled)
- `aria-label` (from `data-label` or auto-generated for range)

`slider-track`, `slider-range`, and `slider-thumb` also mirror the root's
`data-orientation` and `data-disabled` attrs for styling. `slider-control` is optional;
when omitted, the root acts as the interactive control surface.
