Slider

Allows users to select a value from a continuous range by sliding a handle.

llms.txt
	<script lang="ts">
  import { Slider } from "bits-ui";
  import cn from "clsx";
 
  let value = $state(50);
</script>
 
<div class="w-full md:max-w-[280px]">
  <Slider.Root
    type="single"
    bind:value
    class="relative flex w-full touch-none select-none items-center"
  >
    <span
      class="bg-dark-10 relative h-2 w-full grow cursor-pointer overflow-hidden rounded-full"
    >
      <Slider.Range class="bg-foreground absolute h-full" />
    </span>
    <Slider.Thumb
      index={0}
      class={cn(
        "border-border-input bg-background hover:border-dark-40 focus-visible:ring-foreground dark:bg-foreground dark:shadow-card data-active:border-dark-40 focus-visible:outline-hidden data-active:scale-[0.98] block size-[25px] cursor-pointer rounded-full border shadow-sm transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
      )}
    />
  </Slider.Root>
</div>

Structure

	<script lang="ts">
  import { Slider } from "bits-ui";
</script>
 
<Slider.Root>
  <Slider.Range />
  <Slider.Thumb />
  <Slider.Tick />
</Slider.Root>

Reusable Components

Bits UI provides primitives that enable you to build your own custom slider component that can be reused throughout your application.

Here's an example of how you might create a reusable MySlider component.

MyMultiSlider.svelte
	<script lang="ts">
  import type { ComponentProps } from "svelte";
  import { Slider } from "bits-ui";
 
  type Props = WithoutChildren<ComponentProps<typeof Slider.Root>>;
 
  let {
    value = $bindable(),
    ref = $bindable(null),
    ...restProps
  }: Props = $props();
</script>
 
<!--
 Since we have to destructure the `value` to make it `$bindable`, we need to use `as any` here to avoid
 type errors from the discriminated union of `"single" | "multiple"`.
 (an unfortunate consequence of having to destructure bindable values)
  -->
<Slider.Root bind:value bind:ref {...restProps as any}>
  {#snippet children({ thumbs, ticks })}
    <Slider.Range />
    {#each thumbs as index}
      <Slider.Thumb {index} />
    {/each}
 
    {#each ticks as index}
      <Slider.Tick {index} />
    {/each}
  {/snippet}
</Slider.Root>

You can then use the MySlider component in your application like so:

	<script lang="ts">
  import MySlider from "$lib/components/MySlider.svelte";
 
  let multiValue = $state([5, 10]);
  let singleValue = $state(50);
</script>
 
<MySlider bind:value={multiValue} type="multiple" />
<MySlider bind:value={singleValue} type="single" />

Managing Value State

This section covers how to manage the value state of the component.

Two-Way Binding

Use bind:value for simple, automatic state synchronization:

	<script lang="ts">
  import { Slider } from "bits-ui";
  let myValue = $state(0);
</script>
 
<button onclick={() => (myValue = 20)}> Set value to 20 </button>
 
<Slider.Root bind:value={myValue} type="single">
  <!-- ... -->
</Slider.Root>

Fully Controlled

Use a Function Binding for complete control over the state's reads and writes.

	<script lang="ts">
  import { Slider } from "bits-ui";
  let myValue = $state(0);
 
  function getValue() {
    return myValue;
  }
 
  function setValue(newValue: number) {
    myValue = newValue;
  }
</script>
 
<Slider.Root type="single" bind:value={getValue, setValue}>
  <!-- ... -->
</Slider.Root>

Value Commit

You can use the onValueCommit prop to be notified when the user finishes dragging the thumb and the value changes. This is different than the onValueChange callback because it waits until the user stops dragging before calling the callback, where the onValueChange callback is called as the user dragging.

	<Slider.Root
  onValueCommit={(v) => {
    console.log("user is done sliding!", v);
  }}
/>

RTL Support

You can use the dir prop to change the reading direction of the slider, which defaults to "ltr".

	<Slider.Root type="single" dir="rtl">
  <!-- ... -->
</Slider.Root>

Auto Sort

By default, the slider will sort the values from smallest to largest, so if you drag a smaller thumb to a larger value, the value of that thumb will be updated to the larger value.

You can disable this behavior by setting the autoSort prop to false.

	<Slider.Root type="multiple" autoSort={false}>
  <!-- ... -->
</Slider.Root>

HTML Forms

Since there is a near endless number of possible values that a user can select, the slider does not render a hidden input element by default.

You'll need to determine how you want to submit the value(s) of the slider with a form.

Here's an example of how you might do that:

	<script lang="ts">
  import MySlider from "$lib/components/MySlider.svelte";
 
  let expectedIncome = $state([50, 100]);
  let desiredIncome = $state(50);
</script>
 
<form method="POST">
  <MySlider type="multiple" bind:value={expectedIncome} />
  <input type="hidden" name="expectedIncomeStart" value={expectedIncome[0]} />
  <input type="hidden" name="expectedIncomeEnd" value={expectedIncome[1]} />
  <MySlider type="single" bind:value={desiredIncome} />
  <input type="hidden" name="expectedIncomeEnd" value={desiredIncome} />
  <button type="submit">Submit</button>
</form>

Examples

Multiple Thumbs and Ticks

If the value prop has more than one value, the slider will render multiple thumbs. You can also use the tickItems and thumbItems snippet props to render ticks and thumbs at specific intervals.

	<script lang="ts">
  import { Slider } from "bits-ui";
 
  // we have two numbers in the array, so the slider will render two thumbs
  let value = $state([5, 7]);
</script>
 
<Slider.Root type="multiple" min={0} max={10} step={1} bind:value>
  {#snippet children({ tickItems, thumbItems })}
    <Slider.Range />
 
    {#each thumbItems as { index } (index)}
      <Slider.Thumb {index} />
    {/each}
 
    {#each tickItems as { index } (index)}
      <Slider.Tick {index} />
    {/each}
  {/snippet}
</Slider.Root>

To determine the number of ticks that will be rendered, you can simply divide the max value by the step value.

	<script lang="ts">
  import { Slider } from "bits-ui";
 
  let value = $state([5, 7]);
</script>
 
<div class="w-full md:max-w-[280px]">
  <Slider.Root
    step={1}
    min={0}
    max={10}
    type="multiple"
    bind:value
    class="relative flex w-full touch-none select-none items-center"
  >
    {#snippet children({ tickItems, thumbs })}
      <span
        class="bg-dark-10 relative h-2 w-full grow cursor-pointer overflow-hidden rounded-full"
      >
        <Slider.Range class="bg-foreground absolute h-full" />
      </span>
      {#each thumbs as thumb (thumb)}
        <Slider.Thumb
          index={thumb}
          class="border-border-input bg-background hover:border-dark-40 focus-visible:ring-foreground dark:bg-foreground dark:shadow-card data-active:border-dark-40 z-5 focus-visible:outline-hidden data-active:scale-[0.98] block size-[25px] cursor-pointer rounded-full border shadow-sm transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
        />
      {/each}
      {#each tickItems as { index } (index)}
        <Slider.Tick
          {index}
          class="dark:bg-background/20 bg-background z-1 h-2 w-[1px]"
        />
      {/each}
    {/snippet}
  </Slider.Root>
</div>

Single Type

Set the type prop to "single" to allow only one slider handle.

	<Slider.Root type="single" />
	<script lang="ts">
  import { Slider } from "bits-ui";
  import cn from "clsx";
 
  let value = $state(50);
</script>
 
<div class="w-full md:max-w-[280px]">
  <Slider.Root
    type="single"
    bind:value
    class="relative flex w-full touch-none select-none items-center"
  >
    <span
      class="bg-dark-10 relative h-2 w-full grow cursor-pointer overflow-hidden rounded-full"
    >
      <Slider.Range class="bg-foreground absolute h-full" />
    </span>
    <Slider.Thumb
      index={0}
      class={cn(
        "border-border-input bg-background hover:border-dark-40 focus-visible:ring-foreground dark:bg-foreground dark:shadow-card data-active:border-dark-40 focus-visible:outline-hidden data-active:scale-[0.98] block size-[25px] cursor-pointer rounded-full border shadow-sm transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
      )}
    />
  </Slider.Root>
</div>

Multiple Type

Set the type prop to "multiple" to allow multiple slider handles.

	<Slider.Root type="multiple" />
	<script lang="ts">
  import { Slider } from "bits-ui";
  import cn from "clsx";
 
  let value = $state([25, 75]);
</script>
 
<div class="w-full md:max-w-[280px]">
  <Slider.Root
    type="multiple"
    bind:value
    class="relative flex w-full touch-none select-none items-center"
  >
    {#snippet children({ thumbItems })}
      <span
        class="bg-dark-10 relative h-2 w-full grow cursor-pointer overflow-hidden rounded-full"
      >
        <Slider.Range class="bg-foreground absolute h-full" />
      </span>
      {#each thumbItems as { index } (index)}
        <Slider.Thumb
          {index}
          class={cn(
            "border-border-input bg-background hover:border-dark-40 focus-visible:ring-foreground dark:bg-foreground dark:shadow-card data-active:border-dark-40 focus-visible:outline-hidden data-active:scale-[0.98] block size-[25px] cursor-pointer rounded-full border shadow-sm transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
          )}
        />
      {/each}
    {/snippet}
  </Slider.Root>
</div>

Vertical Orientation

You can use the orientation prop to change the orientation of the slider, which defaults to "horizontal".

	<Slider.Root type="single" orientation="vertical">
  <!-- ... -->
</Slider.Root>
	<script lang="ts">
  import { Slider } from "bits-ui";
 
  let value = $state(50);
</script>
 
<div class="flex h-[320px] w-full justify-center">
  <Slider.Root
    type="single"
    step={1}
    bind:value
    orientation="vertical"
    class="relative flex h-full touch-none select-none flex-col items-center"
    trackPadding={3}
  >
    <span
      class="bg-dark-10 relative h-full w-2 cursor-pointer overflow-hidden rounded-full"
    >
      <Slider.Range class="bg-foreground absolute w-full" />
    </span>
    <Slider.Thumb
      index={0}
      class="border-border-input bg-background hover:border-dark-40 focus-visible:ring-foreground dark:bg-foreground dark:shadow-card data-active:border-dark-40 z-5 focus-visible:outline-hidden data-active:scale-[0.98] block size-[25px] cursor-pointer rounded-full border shadow-sm transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
    />
  </Slider.Root>
</div>

Tick Labels

You can use the tickItems snippet prop in combination with the Slider.TickLabel to render labels at specific intervals.

	<Slider.Root type="single" step={[0, 4, 8, 16, 24]}>
  {#snippet children({ tickItems })}
    {#each tickItems as { value, index } (index)}
      <Slider.Tick {index} />
      <Slider.TickLabel {index} position="top">
        {value}
      </Slider.TickLabel>
    {/each}
  {/snippet}
</Slider.Root>
0 1 2 3 4 5 6 7 8 9 10
	<script lang="ts">
  import { Slider } from "bits-ui";
 
  let value = $state([5, 7]);
</script>
 
<div class="w-full md:max-w-[280px]">
  <Slider.Root
    step={1}
    min={0}
    max={10}
    type="multiple"
    bind:value
    class="relative flex w-full touch-none select-none items-center"
    trackPadding={2}
  >
    {#snippet children({ tickItems, thumbItems })}
      <span
        class="bg-dark-10 relative h-2 w-full grow cursor-pointer overflow-hidden rounded-full"
      >
        <Slider.Range class="bg-foreground absolute h-full" />
      </span>
      {#each thumbItems as { index } (index)}
        <Slider.Thumb
          {index}
          class="border-border-input bg-background hover:border-dark-40 focus-visible:ring-foreground dark:bg-foreground dark:shadow-card data-active:border-dark-40 z-5 focus-visible:outline-hidden data-active:scale-[0.98] block size-[25px] cursor-pointer rounded-full border shadow-sm transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
        />
      {/each}
      {#each tickItems as { index, value } (index)}
        <Slider.Tick
          {index}
          class="dark:bg-background/20 bg-background z-1 h-2 w-[1px]"
        />
        <Slider.TickLabel
          {index}
          class="text-muted-foreground data-bounded:text-foreground mb-5 text-sm font-medium leading-none"
          position="top"
        >
          {value}
        </Slider.TickLabel>
      {/each}
    {/snippet}
  </Slider.Root>
</div>

Discrete Steps

Instead of passing a single value to the step prop, you can pass an array of discrete values that the slider will snap to.

	<Slider.Root type="single" step={[0, 4, 8, 16, 24]}>
  <!-- ... -->
</Slider.Root>
0px 4px 8px 16px 24px
0px 4px 8px 16px 24px
	<script lang="ts">
  import { Slider } from "bits-ui";
 
  let fontSize = $state(16);
  const fontSizes = [0, 4, 8, 16, 24];
</script>
 
<div class="w-full md:max-w-[320px]">
  <Slider.Root
    type="single"
    step={fontSizes}
    bind:value={fontSize}
    class="relative flex w-full touch-none select-none items-center"
    trackPadding={3}
  >
    {#snippet children({ tickItems })}
      <span
        class="bg-dark-10 relative h-2 w-full grow cursor-pointer overflow-hidden rounded-full"
      >
        <Slider.Range class="bg-foreground absolute h-full" />
      </span>
      <Slider.Thumb
        index={0}
        class="border-border-input bg-background hover:border-dark-40 focus-visible:ring-foreground dark:bg-foreground dark:shadow-card data-active:border-dark-40 z-5 focus-visible:outline-hidden data-active:scale-[0.98] block size-[25px] cursor-pointer rounded-full border shadow-sm transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
      />
      {#each tickItems as { index, value } (index)}
        <Slider.Tick
          {index}
          class="dark:bg-background bg-background z-1 h-2 w-[1px]"
        />
        <Slider.TickLabel
          {index}
          class="text-muted-foreground data-selected:text-foreground mb-5 text-sm font-medium leading-none"
        >
          {value}px
        </Slider.TickLabel>
      {/each}
    {/snippet}
  </Slider.Root>
</div>
 
<div class="flex h-[320px] w-full justify-center">
  <Slider.Root
    type="single"
    step={fontSizes}
    bind:value={fontSize}
    orientation="vertical"
    class="relative flex h-full touch-none select-none flex-col items-center"
    trackPadding={3}
  >
    {#snippet children({ tickItems })}
      <span
        class="bg-dark-10 relative h-full w-2 cursor-pointer overflow-hidden rounded-full"
      >
        <Slider.Range class="bg-foreground absolute w-full" />
      </span>
      <Slider.Thumb
        index={0}
        class="border-border-input bg-background hover:border-dark-40 focus-visible:ring-foreground dark:bg-foreground dark:shadow-card data-active:border-dark-40 z-5 focus-visible:outline-hidden data-active:scale-[0.98] block size-[25px] cursor-pointer rounded-full border shadow-sm transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
      />
      {#each tickItems as { index, value } (index)}
        <Slider.Tick {index} class="dark:bg-background z-1 h-[1px] w-4" />
        <Slider.TickLabel
          {index}
          class="text-muted-foreground data-selected:text-foreground mr-5 text-sm font-medium leading-none"
        >
          {value}px
        </Slider.TickLabel>
      {/each}
    {/snippet}
  </Slider.Root>
</div>

Thumb Labels

Use the Slider.ThumbLabel component to render a label that is positioned relative to the thumb.

You can manually specify the index like so:

	<Slider.Root type="multiple" autoSort={false} step={10} value={[10, 50]}>
  <Slider.Range />
  <Slider.Thumb index={0} />
  <Slider.ThumbLabel index={0} position="top">Min</Slider.ThumbLabel>
  <Slider.Thumb index={1} />
  <Slider.ThumbLabel index={1} position="top">Max</Slider.ThumbLabel>
</Slider.Root>

or use the thumbItems snippet prop to render a label for each thumb:

	<Slider.Root type="multiple" autoSort={false} step={10} value={[10, 50]}>
  <Slider.Range />
  {#snippet children({ thumbItems })}
    {#each thumbItems as { index, value } (index)}
      <Slider.Thumb {index} />
      <Slider.ThumbLabel {index} position="top">
        {index === 0 ? "Min" : "Max"}:{value}
      </Slider.ThumbLabel>
    {/each}
  {/snippet}
</Slider.Root>
Check in Dinner Sleep 4pm 5pm 6pm 7pm 8pm 9pm 10pm 11pm
	<script lang="ts">
  import { Slider } from "bits-ui";
 
  let value = $state([5, 7, 10]);
</script>
 
<div class="w-full md:max-w-[400px]">
  <Slider.Root
    step={1}
    min={4}
    max={11}
    type="multiple"
    bind:value
    class="relative flex w-full touch-none select-none items-center"
    trackPadding={2}
    autoSort={false}
  >
    {#snippet children({ tickItems })}
      <span
        class="bg-dark-10 relative h-2 w-full grow cursor-pointer overflow-hidden rounded-full"
      >
        <Slider.Range class="bg-foreground absolute h-full" />
      </span>
      <Slider.Thumb
        index={0}
        class="border-border-input bg-background hover:border-dark-40 focus-visible:ring-foreground dark:bg-foreground dark:shadow-card data-active:border-dark-40 z-5 focus-visible:outline-hidden data-active:scale-[0.98] block size-[25px] cursor-pointer rounded-full border shadow-sm transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
      />
      <Slider.ThumbLabel
        index={0}
        class="bg-muted text-foreground mb-5 text-nowrap rounded-md px-2 py-1 text-sm"
      >
        Check in
      </Slider.ThumbLabel>
      <Slider.Thumb
        index={1}
        class="border-border-input bg-background hover:border-dark-40 focus-visible:ring-foreground dark:bg-foreground dark:shadow-card data-active:border-dark-40 z-5 focus-visible:outline-hidden data-active:scale-[0.98] block size-[25px] cursor-pointer rounded-full border shadow-sm transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
      />
      <Slider.ThumbLabel
        index={1}
        class="bg-muted text-foreground mb-5 text-nowrap rounded-md px-2 py-1 text-sm"
      >
        Dinner
      </Slider.ThumbLabel>
      <Slider.Thumb
        index={2}
        class="border-border-input bg-background hover:border-dark-40 focus-visible:ring-foreground dark:bg-foreground dark:shadow-card data-active:border-dark-40 z-5 focus-visible:outline-hidden data-active:scale-[0.98] block size-[25px] cursor-pointer rounded-full border shadow-sm transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
      />
      <Slider.ThumbLabel
        index={2}
        class="bg-muted text-foreground mb-5 text-nowrap rounded-md px-2 py-1 text-sm"
      >
        Sleep
      </Slider.ThumbLabel>
      {#each tickItems as { index, value } (index)}
        <Slider.Tick
          {index}
          class="dark:bg-background/20 bg-background z-1 h-2 w-[1px]"
        />
        <Slider.TickLabel
          {index}
          class="text-muted-foreground data-selected:text-foreground mt-5 text-xs font-medium leading-none"
          position="bottom"
        >
          {value}pm
        </Slider.TickLabel>
      {/each}
    {/snippet}
  </Slider.Root>
</div>

API Reference

Slider.Root

The root slider component which contains the remaining slider components.

Property Details
type
value
onValueChange
onValueCommit
disabled
max
min
orientation
step
dir
autoSort
thumbPositioning
trackPadding
ref
children
child
Data Attribute Details
data-orientation
data-disabled
data-slider-root

Slider.Range

The range of the slider.

Property Details
ref
children
child
Data Attribute Details
data-orientation
data-disabled
data-slider-range

Slider.Thumb

A thumb on the slider.

Property Details
index
disabled
ref
children
child
Data Attribute Details
data-orientation
data-disabled
data-active
data-slider-thumb

Slider.ThumbLabel

A label for a thumb on the slider.

Property Details
index
position
ref
children
child
Data Attribute Details
data-orientation
data-disabled
data-position
data-active
data-value
data-slider-thumb-label

Slider.Tick

A tick mark on the slider.

Property Details
index
ref
children
child
Data Attribute Details
data-orientation
data-disabled
data-bounded
data-value
data-selected
data-slider-tick

Slider.TickLabel

A label for a tick on the slider.

Property Details
index
position
ref
children
child
Data Attribute Details
data-orientation
data-disabled
data-position
data-selected
data-value
data-bounded
data-slider-tick-label