Slider

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

	<script lang="ts">
  import { Slider } from "bits-ui";
  import { cn } from "$lib/utils/styles.js";
 
  let value = [20, 80];
</script>
 
<div class="w-full md:max-w-[280px]">
  <Slider.Root
    step={25}
    bind:value
    class="relative flex w-full touch-none select-none items-center"
  >
    {#snippet children({ thumbs, ticks })}
      <span
        class="relative h-2 w-full grow overflow-hidden rounded-full bg-dark-10"
      >
        <Slider.Range class="absolute h-full bg-foreground" />
      </span>
      {#each thumbs as index}
        <Slider.Thumb
          {index}
          class={cn(
            "block size-[25px] cursor-pointer rounded-full border border-border-input bg-background shadow transition-colors hover:border-dark-40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground focus-visible:ring-offset-2 active:scale-98 disabled:pointer-events-none disabled:opacity-50 dark:shadow-card",
            index === 0 ? "bg-blue-400" : "bg-yellow-300"
          )}
        />
      {/each}
      {#each ticks as index}
        <Slider.Tick {index} class="block h-1 w-1  bg-dark-10" />
      {/each}
    {/snippet}
  </Slider.Root>
</div>

Structure

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

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

MySlider.svelte
	<script lang="ts">
	import { Slider } from "bits-ui";
 
	type Props = WithoutChildren<Slider.RootProps>;
 
	let { value = $bindable(), ref = $bindable(null), ...restProps }: Props = $props();
</script>
 
<Slider.Root bind:value bind:ref {...restProps}>
	{#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 someValue = $state([5, 10]);
</script>
 
<MySlider bind:value={someValue} />

Managing Value State

Bits UI offers several approaches to manage and synchronize the Slider's value state, catering to different levels of control and integration needs.

1. Two-Way Binding

For seamless state synchronization, use Svelte's bind:value directive. This method automatically keeps your local state in sync with the component's internal state.

	<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}>
	<!-- ... -->
</Slider.Root>

Key Benefits

  • Simplifies state management
  • Automatically updates myValue when the internal state changes (e.g., via dragging the thumb(s))
  • Allows external control (e.g., updating the value via a separate button)

2. Change Handler

For more granular control or to perform additional logic on state changes, use the onValueChange prop. This approach is useful when you need to execute custom logic alongside state updates.

	<script lang="ts">
	import { Slider } from "bits-ui";
	let myValue = $state([0]);
</script>
 
<Slider.Root
	value={myValue}
	onValueChange={(v) => {
		myValue = v;
		// additional logic here.
	}}
>
	<!-- ... -->
</Slider.Root>

Use Cases

  • Implementing custom behaviors on value change
  • Integrating with external state management solutions
  • Triggering side effects (e.g., logging, data fetching)

3. Fully Controlled

For complete control over the component's value state, use the controlledValue prop. This approach requires you to manually manage the value state, giving you full control over when and how the component responds to value change events.

To implement controlled state:

  1. Set the controlledValue prop to true on the Slider.Root component.
  2. Provide a value prop to Slider.Root, which should be a variable holding the current state.
  3. Implement an onValueChange handler to update the state when the internal state changes.
	<script lang="ts">
	import { Slider } from "bits-ui";
	let myValue = $state([0]);
</script>
 
<Slider.Root controlledValue value={myValue} onValueChange={(v) => (myValue = v)}>
	<!-- ... -->
</Slider.Root>

When to Use

  • Implementing complex logic
  • Coordinating multiple UI elements
  • Debugging state-related issues

Multiple Thumbs and Ticks

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

	<script lang="ts">
	import { Slider } from "bits-ui";
 
	let value = $state([5, 7]);
</script>
 
<Slider.Root min={0} max={10} step={1} bind:value>
	{#snippet children({ ticks, thumbs })}
		<Slider.Range />
 
		{#each thumbs as index}
			<Slider.Thumb {index} />
		{/each}
 
		{#each ticks as 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.

Vertical Orientation

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

	<Slider.Root orientation="vertical">
	<!-- ... -->
</Slider.Root>

RTL Support

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

	<Slider.Root 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 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]);
</script>
 
<form method="POST">
	<MySlider bind:value={expectedIncome} />
	<input type="hidden" name="expectedIncomeStart" value={expectedIncome[0]} />
	<input type="hidden" name="expectedIncomeEnd" value={expectedIncome[1]} />
	<button type="submit">Submit</button>
</form>

API Reference

Slider.Root

The root slider component which contains the remaining slider components.

Property Type Description
value $bindable
number[]

The current value of the slider.

Default: []
onValueChange
function

A callback function called when the value state of the slider changes.

Default: undefined
onValueChangeEnd
function

A callback function called 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 immediately after the user starts dragging.

Default: undefined
controlledValue
boolean

Whether or not the value is controlled or not. If true, the component will not update the value state internally, instead it will call onValueChange when it would have otherwise, and it is up to you to update the value prop that is passed to the component.

Default: false
disabled
boolean

Whether or not the switch is disabled.

Default: false
max
number

The maximum value of the slider.

Default: 100
min
number

The minimum value of the slider.

Default: 0
orientation
enum

The orientation of the slider.

Default: "horizontal"
step
number

The step value of the slider.

Default: 1
dir
enum

The reading direction of the app.

Default: ltr
autoSort
boolean

Whether to automatically sort the values in the array when moving thumbs past one another.

Default: true
ref $bindable
HTMLSpanElement

The underlying DOM element being rendered. You can bind to this to get a reference to the element.

Default: undefined
children
Snippet

The children content to render.

Default: undefined
child
Snippet

Use render delegation to render your own element. See delegation docs for more information.

Default: undefined
Data Attribute Value Description
data-orientation
enum

The orientation of the slider.

data-slider-root
''

Present on the root element.

Slider.Range

The range of the slider.

Property Type Description
ref $bindable
HTMLSpanElement

The underlying DOM element being rendered. You can bind to this to get a reference to the element.

Default: undefined
children
Snippet

The children content to render.

Default: undefined
child
Snippet

Use render delegation to render your own element. See delegation docs for more information.

Default: undefined
Data Attribute Value Description
data-slider-range
''

Present on the range elements.

Slider.Thumb

A thumb on the slider.

Property Type Description
index required
number

The index of the thumb in the array of thumbs provided by the thumbs children snippet prop.

Default: undefined
disabled
boolean

Whether or not the thumb is disabled.

Default: false
ref $bindable
HTMLSpanElement

The underlying DOM element being rendered. You can bind to this to get a reference to the element.

Default: undefined
children
Snippet

The children content to render.

Default: undefined
child
Snippet

Use render delegation to render your own element. See delegation docs for more information.

Default: undefined
Data Attribute Value Description
data-slider-thumb
''

Present on the thumb elements.

Slider.Tick

A tick mark on the slider.

Property Type Description
index required
number

The index of the tick in the array of ticks provided by the ticks children snippet prop.

Default: undefined
ref $bindable
HTMLSpanElement

The underlying DOM element being rendered. You can bind to this to get a reference to the element.

Default: undefined
children
Snippet

The children content to render.

Default: undefined
child
Snippet

Use render delegation to render your own element. See delegation docs for more information.

Default: undefined
Data Attribute Value Description
data-bounded
''

Present when the tick is bounded.

data-slider-tick
''

Present on the tick elements.