Skip to content

Slider

An input where the user selects a value from within a given range.
vue
<script setup lang="ts">
import { ref } from 'vue'
import { SliderRange, SliderRoot, SliderThumb, SliderTrack } from 'radix-vue'

const sliderValue = ref([50])
</script>

<template>
  <SliderRoot
    v-model="sliderValue" class="relative flex items-center select-none touch-none w-[200px] h-5" :max="100"
    :step="1"
  >
    <SliderTrack class="bg-blackA10 relative grow rounded-full h-[3px]">
      <SliderRange class="absolute bg-white rounded-full h-full" />
    </SliderTrack>
    <SliderThumb
      class="block w-5 h-5 bg-white shadow-[0_2px_10px] shadow-blackA7 rounded-[10px] hover:bg-violet3 focus:outline-none focus:shadow-[0_0_0_5px] focus:shadow-blackA8"
      aria-label="Volume"
    />
  </SliderRoot>
</template>

Features

  • Can be controlled or uncontrolled.
  • Supports multiple thumbs.
  • Supports a minimum value between thumbs.
  • Supports touch or click on track to update value.
  • Supports Right to Left direction.
  • Full keyboard navigation.

Installation

Install the component from your command line.

sh
$ npm add radix-vue

Anatomy

Import all parts and piece them together.

vue
<script setup>
import { SliderRange, SliderRoot, SliderThumb, SliderTrack } from 'radix-vue'
</script>

<template>
  <SliderRoot>
    <SliderTrack>
      <SliderRange />
    </SliderTrack>
    <SliderThumb />
  </SliderRoot>
</template>

API Reference

Root

Contains all the parts of a slider. It will render an input for each thumb when used within a form to ensure events propagate correctly.

PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

asChild
boolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

defaultValue
[0]
number[]

The value of the slider when initially rendered. Use when you do not need to control the state of the slider.

dir
'ltr' | 'rtl'

The reading direction of the combobox when applicable.
If omitted, inherits globally from DirectionProvider or assumes LTR (left-to-right) reading mode.

disabled
false
boolean

When true, prevents the user from interacting with the slider.

inverted
false
boolean

Whether the slider is visually inverted.

max
100
number

The maximum value for the range.

min
0
number

The minimum value for the range.

minStepsBetweenThumbs
0
number

The minimum permitted steps between multiple thumbs.

modelValue
number[]

The controlled value of the slider. Can be bind as v-model.

name
string
orientation
'horizontal'
'vertical' | 'horizontal'

The orientation of the slider.

step
1
number

The stepping interval.

EmitPayload
update:modelValue
[payload: number[]]

Event handler called when the slider value changes

valueCommit
[payload: number[]]

Event handler called when the value changes at the end of an interaction.

Useful when you only need to capture a final value e.g. to update a backend service.

Slots (default)Payload
modelValue
number[]

Current slider values

Data AttributeValue
[data-disabled]Present when disabled
[data-orientation]"vertical" | "horizontal"

Track

The track that contains the SliderRange.

PropDefaultType
as
'span'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

asChild
boolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

Data AttributeValue
[data-disabled]Present when disabled
[data-orientation]"vertical" | "horizontal"

Range

The range part. Must live inside SliderTrack.

PropDefaultType
as
'span'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

asChild
boolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

Data AttributeValue
[data-disabled]Present when disabled
[data-orientation]"vertical" | "horizontal"

Thumb

A draggable thumb. You can render multiple thumbs.

PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

asChild
boolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

Data AttributeValue
[data-disabled]Present when disabled
[data-orientation]"vertical" | "horizontal"

Examples

Vertical orientation

Use the orientation prop to create a vertical slider.

vue
// index.vue
<script setup>
import { SliderRange, SliderRoot, SliderThumb, SliderTrack } from 'radix-vue'
</script>

<template>
  <SliderRoot class="SliderRoot" :default-value="[50]" orientation="vertical">
    <SliderTrack class="SliderTrack">
      <SliderRange class="SliderRange" />
    </SliderTrack>
    <SliderThumb class="SliderThumb" />
  </SliderRoot>
</template>
css
/* styles.css */
.SliderRoot {
  position: relative;
  display: flex;
  align-items: center;
}
.SliderRoot[data-orientation="vertical"] {
  flex-direction: column;
  width: 20px;
  height: 100px;
}

.SliderTrack {
  position: relative;
  flex-grow: 1;
  background-color: grey;
}
.SliderTrack[data-orientation="vertical"] {
  width: 3px;
}

.SliderRange {
  position: absolute;
  background-color: black;
}
.SliderRange[data-orientation="vertical"] {
  width: 100%;
}

.SliderThumb {
  display: block;
  width: 20px;
  height: 20px;
  background-color: black;
}

Create a range

Add multiple thumbs and values to create a range slider.

vue
// index.vue
<script setup>
import { SliderRange, SliderRoot, SliderThumb, SliderTrack } from 'radix-vue'
</script>

<template>
  <SliderRoot :default-value="[25, 75]">
    <SliderTrack>
      <SliderRange />
    </SliderTrack>
    <SliderThumb />
    <SliderThumb />
  </SliderRoot>
</template>

Define step size

Use the step prop to increase the stepping interval.

vue
// index.vue
<script setup>
import { SliderRange, SliderRoot, SliderThumb, SliderTrack } from 'radix-vue'
</script>

<template>
  <SliderRoot :default-value="[50]" :step="10">
    <SliderTrack>
      <SliderRange />
    </SliderTrack>
    <SliderThumb />
  </SliderRoot>
</template>

Prevent thumb overlap

Use minStepsBetweenThumbs to avoid thumbs with equal values.

vue
// index.vue
<script setup>
import { SliderRange, SliderRoot, SliderThumb, SliderTrack } from 'radix-vue'
</script>

<template>
  <SliderRoot
    :default-value="[25, 75]"
    :step="10"
    :min-steps-between-thumbs="1"
  >
    <SliderTrack>
      <SliderRange />
    </SliderTrack>
    <SliderThumb />
    <SliderThumb />
  </SliderRoot>
</template>

Accessibility

Adheres to the Slider WAI-ARIA design pattern.

Keyboard Interactions

KeyDescription
ArrowRight
Increments/decrements by the step value depending on orientation.
ArrowLeft
Increments/decrements by the step value depending on orientation.
ArrowUp
Increases the value by the step amount.
ArrowDown
Decreases the value by the step amount.
PageUp
Increases the value by a larger step.
PageDown
Decreases the value by a larger step.
Shift + ArrowUp
Increases the value by a larger step.
Shift + ArrowDown
Decreases the value by a larger step.
Home
Sets the value to its minimum.
End
Sets the value to its maximum.

Custom APIs

Create your own API by abstracting the primitive parts into your own component.

Abstract all parts

This example abstracts all of the Slider parts so it can be used as a self closing element.

Usage

vue
<script setup lang="ts">
import { Slider } from './your-slider'
</script>

<template>
  <Slider :default-value="[25]" />
</template>

Implementation

ts
// your-slider.ts
export { default as Slider } from 'Slider.vue'
vue
 <!-- Slider.vue -->
<script setup lang="ts">
import { SlideRoot, SliderRange, type SliderRootEmits, type SliderRootProps, SliderThumb, SliderTrack, useForwardPropsEmits } from 'radix-vue'

const props = defineProps<SliderRootProps>()
const emits = defineEmits<SliderRootEmits>()

const forward = useForwardPropsEmits(props, emits)
</script>

<template>
  <SliderRoot v-bind="forward">
    <SliderTrack>
      <SliderRange />
    </SliderTrack>

    <SliderThumb v-for="(_, i) in value" :key="i" />
  </SliderRoot>
</template>

Caveats

Mouse events are not fired

Because of a limitation we faced during implementation, the following example won't work as expected and the @mousedown and @mousedown event handlers won't be fired:

vue
<SliderRoot
  @mousedown="() => { console.log('onMouseDown')  }"
  @mouseup="() => { console.log('onMouseUp')  }"
>

</SliderRoot>

We recommend using pointer events instead (eg. @pointerdown, @pointerup). Regardless of the above limitation, these events are better suited for cross-platform/device handling as they are fired for all pointer input types (mouse, touch, pen, etc.).