Slider

Scrub a numeric value by dragging anywhere in the field. The label and value sit inside the control, so a panel of adjustments reads as one column.

<script lang="ts">
    import { Slider } from '@sivir-ui/svelte/components/slider';

    let quality = $state(2);
    let strength = $state(100);
    let opacity = $state(72);

    function formatQuality(value: number) {
        return `${value}k`;
    }

    function formatPercent(value: number) {
        return `${value}%`;
    }
</script>

<div class="flex w-full max-w-xs flex-col gap-3">
    <Slider bind:value={quality} min={1} max={4} label="Quality" format={formatQuality} />
    <Slider bind:value={strength} label="Strength" format={formatPercent} editable />
    <Slider bind:value={opacity} label="Opacity" format={formatPercent} />
</div>

Installation

Copy the command below and run it in your terminal.

bunx @sivir-ui/svelte add slider

Usage

import { Slider } from '@sivir-ui/svelte/components/slider';

let opacity = $state(72);

<Slider
    bind:value={opacity}
    label="Opacity"
    format={(value) => `${value}%`}
/>

The value runs from 0 to 100 in steps of 1 unless you set min, max, and step. Pass format to display units; screen readers hear the same text. Use onValueChange for live previews and onValueCommit for work that should run once, when the pointer is released or a key changes the value. Pass name to submit the value with a form.

Interactions

  • The fill springs to the pressed point, then tracks the pointer closely while you drag. Coarse steps glide between stops instead of jumping.
  • Dragging past either end stretches the field against the limit, and it settles back on release. Pressing an arrow key at the limit gives the same small push.
  • The thumb grows on hover and press, and fades out while it passes behind the label or value so the text stays legible.
  • On touch, the field waits for a horizontal drag or a tap, so vertical scrolling over it never changes the value.
  • Arrow keys move by step, Shift with an arrow moves by ten steps, and Home and End jump to the limits.
  • With editable, clicking the value, or pressing Enter while the slider is focused, turns it into a text field. Enter or blur commits the number, snapped to step and clamped to the limits, and Escape cancels. Units such as px are ignored; pass parse to read other formats. Pressing on the value edits instead of scrubbing.
  • With reduced motion, the fill moves instantly and the field never stretches.

Composition

A slider without children renders Range, Thumb, Label (when label is set), and Value. Import the parts as a namespace and compose them to omit, reorder, or restyle one. The root is a label element, so Label text names the slider; without it, pass label to the root.

import * as Slider from '@sivir-ui/svelte/components/slider';

let exposure = $state(0);

const format = (value) => `${value.toFixed(1)} EV`;

<Slider.Root bind:value={exposure} min={-2} max={2} step={0.1} {format}>
    <Slider.Range />
    <Slider.Thumb />
    <Slider.Label>Exposure</Slider.Label>
    <Slider.Value />
</Slider.Root>

This slider drops the thumb, tints the range, puts the value first, and applies the exposure only when the drag ends.

Applied +0.4 EV

<script lang="ts">
    import * as Slider from '@sivir-ui/svelte/components/slider';

    let exposure = $state(0.4);
    let applied = $state(0.4);

    function formatStops(value: number) {
        const sign = value > 0 ? '+' : '';

        return `${sign}${value.toFixed(1)} EV`;
    }

    function apply(value: number) {
        applied = value;
    }
</script>

<div class="flex w-full max-w-xs flex-col gap-2">
    <Slider.Root
        bind:value={exposure}
        min={-2}
        max={2}
        step={0.1}
        format={formatStops}
        onValueCommit={apply}
    >
        <Slider.Range class="bg-primary/15 group-hover/slider:bg-primary/20" />
        <Slider.Value />
        <Slider.Label>Exposure</Slider.Label>
    </Slider.Root>
    <p class="text-label text-foreground-muted">
        Applied <span class="font-mono text-foreground">{formatStops(applied)}</span>
    </p>
</div>

Examples

With step and units

<script lang="ts">
    import { Slider } from '@sivir-ui/svelte/components/slider';

    let reminder = $state(3);

    function formatDays(value: number) {
        return value === 1 ? '1 day' : `${value} days`;
    }
</script>

<div class="w-full max-w-xs">
    <Slider bind:value={reminder} min={1} max={14} step={1} label="Reminder" format={formatDays} />
</div>

Disabled

<script lang="ts">
    import { Slider } from '@sivir-ui/svelte/components/slider';

    function formatPercent(value: number) {
        return `${value}%`;
    }
</script>

<div class="w-full max-w-xs">
    <Slider value={40} disabled label="Grain" format={formatPercent} />
</div>

Anatomy

Slider.Root - Scrubs a labeled value by dragging the field.

Slider.Range - Fills the field up to the value.

Slider.Thumb - Marks the value edge and reacts to press.

Slider.Label - Names the value inside the field.

Slider.Value - Shows the formatted value.