# Slider

Single-value and two-handle range slider with pill handles, a scrub field variant, keyboard controls, and RTL support.

- Package: `@mielui/svelte`
- Component version: `1.3.0`
- Depends on Mielui components: _internal/utils
- Shared utilities: utils.cn

## Install

~~~~sh
pnpm dlx @mielui/svelte add slider
~~~~

## API

This reference is generated at build time from the component manifest, public `index.ts`, and documentation examples below. Changes to those source files are reflected here in the published Markdown. Standard Svelte and HTML attributes accepted by the exported prop types are supported.


### Slider

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| min | number \| undefined | 0 | No | No | Smallest allowed value. |
| max | number \| undefined | 100 | No | No | Largest allowed value. |
| step | number \| undefined | 1 | No | No | Amount each step changes the value by. |
| disabled | boolean \| undefined | false | No | No | Prevents interaction and dims the control. |
| label | string \| undefined | - | No | No | Names the handle. The field variant also shows it inside the control. |
| format | ((value: number) => string) \| undefined | - | No | No | Formats a value for assistive technology and for the field variant's readout. |
| dir | "ltr" \| "rtl" \| undefined | - | No | No | Text direction. It flips the track for right-to-left. |
| id | string \| undefined | generatedId | No | No | Id of the element. |
| name | string \| undefined | - | No | No | Field name submitted with the form. |
| form | string \| undefined | - | No | No | Id of the form this control belongs to when it sits outside that form. |
| element | HTMLDivElement \| undefined | - | No | Yes | Bindable reference to the DOM element. |
| labels | SliderLabels \| undefined | - | No | No | Overrides the built-in text and accessible names. Every key is optional and English is the fallback. |
| aria-label | string \| undefined | - | No | No | Accessible name when there is no visible label. |
| aria-labelledby | string \| undefined | - | No | No | Id of the element that labels it. |
| aria-describedby | string \| undefined | - | No | No | Id of the element that describes it. |
| class | string \| undefined | - | No | No | Classes added to the element. |
| range | false \| true \| undefined | - | No | No | Uses two thumbs and makes `value` a pair. |
| variant | SliderVariant \| undefined | - | No | No | `field` puts the label and value inside a bar you drag anywhere to scrub. |
| value | number \| [number, number] \| undefined | - | No | Yes | Current value, or a pair in range mode. Bindable. |
| thumbLabels | [string, string] \| undefined | - | No | No | Accessible names of the two thumbs in range mode. |
| onValueChange | ((value: number) => void) \| ((value: [number, number]) => void) \| undefined | - | No | No | Called with the new value when it changes. |
| onValueCommit | ((value: number) => void) \| ((value: [number, number]) => void) \| undefined | - | No | No | Called once when a drag ends with a new value, and after each keyboard or typed change. |
| editable | boolean \| undefined | - | No | No | Field variant only. Lets people click the value, or press Enter, and type an exact number. Typed numbers snap to `step` and clamp to `min` and `max`. |
| parse | ((text: string) => number \| null) \| undefined | - | No | No | Turns typed text into a number for `editable`. Return `null` to reject it. Defaults to the first number in the text, so units such as `px` are ignored. |



### Public types

~~~~ts
import type { DefaultProps } from '@mielui/svelte/utils';
import Slider from './slider.svelte';

export type SliderVariant = 'default' | 'field';

type SliderBaseProps = {
    /** Smallest allowed value. */
    min?: number;
    /** Largest allowed value. */
    max?: number;
    /** Amount each step changes the value by. */
    step?: number;
    /** Prevents interaction and dims the control. */
    disabled?: boolean;
    /** Names the handle. The field variant also shows it inside the control. */
    label?: string;
    /** Formats a value for assistive technology and for the field variant's readout. */
    format?: (value: number) => string;
    /** Text direction. It flips the track for right-to-left. */
    dir?: 'ltr' | 'rtl';
    /** Id of the element. */
    id?: string;
    /** Field name submitted with the form. */
    name?: string;
    /** Id of the form this control belongs to when it sits outside that form. */
    form?: string;
    /** Bindable reference to the DOM element. */
    element?: HTMLDivElement;
    /**
     * Overrides the built-in text and accessible names. Every key is optional and English is the
     * fallback.
     */
    labels?: SliderLabels;
    /** Accessible name when there is no visible label. */
    'aria-label'?: string;
    /** Id of the element that labels it. */
    'aria-labelledby'?: string;
    /** Id of the element that describes it. */
    'aria-describedby'?: string;
} & Omit<DefaultProps, 'children'>;

export type SliderLabels = {
    /** Name of a range that has no label of its own. */
    range?: string;
    /** Name of a range's first thumb. `thumbLabels` wins. */
    minimum?: string;
    /** Name of a range's second thumb. `thumbLabels` wins. */
    maximum?: string;
    /** Accessible name of the value input. `name` is the slider's label, when it has one. */
    value?: (name: string | undefined) => string;
    /** Accessible name of the value button that starts editing. */
    edit?: (name: string | undefined) => string;
};

export type SliderProps = SliderBaseProps &
    (
        | {
              /** Uses two thumbs and makes `value` a pair. */
              range?: false;
              /** `field` puts the label and value inside a bar you drag anywhere to scrub. */
              variant?: SliderVariant;
              /** Current value, or a pair in range mode. Bindable. */
              value?: number;
              /** Accessible names of the two thumbs in range mode. */
              thumbLabels?: never;
              /** Called with the new value when it changes. */
              onValueChange?: (value: number) => void;
              /** Called once when a drag ends with a new value, and after each keyboard or typed change. */
              onValueCommit?: (value: number) => void;
              /**
               * Field variant only. Lets people click the value, or press Enter, and type an exact
               * number. Typed numbers snap to `step` and clamp to `min` and `max`.
               */
              editable?: boolean;
              /**
               * Turns typed text into a number for `editable`. Return `null` to reject it. Defaults
               * to the first number in the text, so units such as `px` are ignored.
               */
              parse?: (text: string) => number | null;
          }
        | {
              /** Uses two thumbs and makes `value` a pair. */
              range: true;
              variant?: never;
              /** Current value, or a pair in range mode. Bindable. */
              value?: [number, number];
              /** Accessible names of the two thumbs in range mode. */
              thumbLabels?: [string, string];
              /** Called with the new value when it changes. */
              onValueChange?: (value: [number, number]) => void;
              /** Called once when a drag ends with a new pair, and after each keyboard change. */
              onValueCommit?: (value: [number, number]) => void;
              editable?: never;
              parse?: never;
          }
    );

export { Slider };
export default Slider;
~~~~

## Examples

### Basic

~~~~svelte
<script lang="ts">
    import { Slider } from '@mielui/svelte/components/slider';
</script>

<div class="w-full max-w-sm">
    <Slider value={50} label="Slider" />
</div>
~~~~

### Disabled

~~~~svelte
<script lang="ts">
    import { Slider } from '@mielui/svelte/components/slider';
</script>

<div class="w-full max-w-sm">
    <Slider value={50} disabled label="Disabled" />
</div>
~~~~

### Field

~~~~svelte
<script lang="ts">
    import { Slider } from '@mielui/svelte/components/slider';

    let quality = $state(2);
    let strength = $state(100);
    let opacity = $state(72);
    let committed = $state(72);
</script>

<div class="flex w-full max-w-xs flex-col gap-3">
    <div class="flex flex-col gap-1.5">
        <Slider
            bind:value={quality}
            variant="field"
            label="Quality"
            min={1}
            max={8}
            format={(value) => `${value}k`}
        />
        <Slider
            bind:value={strength}
            variant="field"
            label="Strength"
            format={(value) => `${value}%`}
        />
        <Slider
            bind:value={opacity}
            variant="field"
            label="Opacity"
            editable
            format={(value) => `${value}%`}
            onValueCommit={(value) => {
                committed = value;
            }}
        />
    </div>
    <p class="text-sm text-foreground-muted">
        Click the opacity value to type one. Last committed: {committed}%
    </p>
</div>
~~~~

### Range

~~~~svelte
<script lang="ts">
    import { numberShuffle } from '@mielui/svelte/actions/number-shuffle';
    import { Slider } from '@mielui/svelte/components/slider';

    let price = $state<[number, number]>([200, 750]);
</script>

<div class="flex w-full max-w-sm flex-col gap-2">
    <div class="flex items-center justify-between text-sm">
        <span class="text-foreground-muted">Price range</span>
        <span class="tabular-nums">
            $<span use:numberShuffle={{ value: price[0] }}>{price[0]}</span>
            to $<span use:numberShuffle={{ value: price[1] }}>
                {price[1]}
            </span>
        </span>
    </div>
    <Slider
        range
        bind:value={price}
        min={0}
        max={1000}
        step={10}
        thumbLabels={['Minimum price', 'Maximum price']}
    />
</div>
~~~~

### Rtl

~~~~svelte
<script lang="ts">
    import { numberShuffle } from '@mielui/svelte/actions/number-shuffle';
    import { Slider } from '@mielui/svelte/components/slider';

    let selection = $state<[number, number]>([25, 70]);
</script>

<div class="flex w-full max-w-sm flex-col gap-2">
    <div class="flex items-center justify-between text-sm">
        <span class="text-foreground-muted">Right-to-left range</span>
        <span class="tabular-nums">
            <span use:numberShuffle={{ value: selection[0] }}>{selection[0]}</span>
            {' '}
            to{' '}
            <span use:numberShuffle={{ value: selection[1] }}>{selection[1]}</span>
        </span>
    </div>
    <Slider range dir="rtl" bind:value={selection} thumbLabels={['Range start', 'Range end']} />
</div>
~~~~

### Stepped

~~~~svelte
<script lang="ts">
    import { Slider } from '@mielui/svelte/components/slider';
</script>

<div class="w-full max-w-sm">
    <Slider value={50} step={10} min={0} max={100} label="By tens" />
</div>
~~~~

### Value Readout

~~~~svelte
<script lang="ts">
    import { numberShuffle } from '@mielui/svelte/actions/number-shuffle';
    import { Slider } from '@mielui/svelte/components/slider';

    let volume = $state(64);
</script>

<div class="flex w-full max-w-sm flex-col gap-2">
    <div class="flex items-center justify-between text-[0.78rem]">
        <span class="text-foreground-muted">Volume</span>
        <span class="font-mono text-foreground">
            <span use:numberShuffle={{ value: volume }}>{volume}</span>
        </span>
    </div>
    <Slider bind:value={volume} label="Volume" />
</div>
~~~~

For the rendered reference, visit [/docs/components/slider](/docs/components/slider).
