<script setup lang="ts">
import { ASliderRange, ASliderRoot, ASliderThumb, ASliderTrack } from 'akar';
import { ref } from 'vue';
const value = ref([50]);
</script>
<template>
<ASliderRoot
v-model="value"
class="flex w-full select-none items-center relative touch-none"
:max="100"
:step="1"
>
<ASliderTrack class="rounded-full bg-background-accented grow h-[8px] relative overflow-hidden">
<ASliderRange class="rounded-full bg-primary h-full absolute" />
</ASliderTrack>
<ASliderThumb
class="rounded-full bg-background size-4 ring-2 ring-primary focus-visible:(outline-2 outline-primary/50 outline-offset-2)"
aria-label="Volume"
/>
</ASliderRoot>
</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.
Anatomy
Import all parts and piece them together.
<script setup>
import { ASliderRange, ASliderRoot, ASliderThumb, ASliderTrack } from 'akar';
</script>
<template>
<ASliderRoot>
<ASliderTrack>
<ASliderRange />
</ASliderTrack>
<ASliderThumb />
</ASliderRoot>
</template>
Pohon
One benefit of using Akar is its flexibility and low-level control over the components. However, this also means that you may need to manually construct more complex UI elements by combining multiple Akar components together.
If you feel there's a lot of elements that needs to be constructed manually using Akar, consider using Pohon UI instead. It provides a higher-level abstraction over Akar components with pre-defined styles and behaviors that can help you build UIs faster.
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.
Props
| Prop | Default | Type |
|---|---|---|
as | 'span' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange 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. | |
disabled | false | booleanWhen |
inverted | false | booleanWhether the slider is visually inverted. |
max | 100 | numberThe maximum value for the range. |
min | 0 | numberThe minimum value for the range. |
minStepsBetweenThumbs | 0 | numberThe minimum permitted steps between multiple thumbs. |
modelValue | number[] | nullThe controlled value of the slider. Can be bind as | |
name | stringThe name of the field. Submitted with its owning form as part of a name/value pair. | |
orientation | 'horizontal' | 'horizontal' | 'vertical'The orientation of the slider. |
required | booleanWhen | |
step | 1 | numberThe stepping interval. |
thumbAlignment | 'contain' | 'contain' | 'overflow'The alignment of the slider thumb.
|
Emits
| Event | Type |
|---|---|
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. |
Slots
| Slot | Type |
|---|---|
modelValue | number[] | nullThe controlled value of the slider. Can be bind as |
Data Attributes
| Attribute | Value |
|---|---|
[data-disabled] | Present when disabled |
[data-orientation] | 'vertical' | 'horizontal' |
Track
The track that contains the ASliderRange.
Props
| Prop | Default | Type |
|---|---|---|
as | 'span' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Data Attributes
| Attribute | Value |
|---|---|
[data-disabled] | Present when disabled |
[data-orientation] | 'vertical' | 'horizontal' |
Range
The range part. Must live inside ASliderTrack.
Props
| Prop | Default | Type |
|---|---|---|
as | 'span' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Data Attributes
| Attribute | Value |
|---|---|
[data-disabled] | Present when disabled |
[data-orientation] | 'vertical' | 'horizontal' |
Thumb
A draggable thumb. You can render multiple thumbs.
Props
| Prop | Default | Type |
|---|---|---|
as | 'span' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Data Attributes
| Attribute | Value |
|---|---|
[data-disabled] | Present when disabled |
[data-orientation] | 'vertical' | 'horizontal' |
Examples
Vertical orientation
Use the orientation prop to create a vertical slider.
// index.vue
<script setup>
import { ASliderRange, ASliderRoot, ASliderThumb, ASliderTrack } from 'akar';
</script>
<template>
<ASliderRoot
class="ASliderRoot"
:default-value="[50]"
orientation="vertical"
>
<ASliderTrack class="ASliderTrack">
<ASliderRange class="ASliderRange" />
</ASliderTrack>
<ASliderThumb class="ASliderThumb" />
</ASliderRoot>
</template>
/* styles.css */
.ASliderRoot {
position: relative;
display: flex;
align-items: center;
}
.ASliderRoot[data-orientation='vertical'] {
flex-direction: column;
width: 20px;
height: 100px;
}
.ASliderTrack {
position: relative;
flex-grow: 1;
background-color: grey;
}
.ASliderTrack[data-orientation='vertical'] {
width: 3px;
}
.ASliderRange {
position: absolute;
background-color: black;
}
.ASliderRange[data-orientation='vertical'] {
width: 100%;
}
.ASliderThumb {
display: block;
width: 20px;
height: 20px;
background-color: black;
}
Create a range
Add multiple thumbs and values to create a range slider.
// index.vue
<script setup>
import { ASliderRange, ASliderRoot, ASliderThumb, ASliderTrack } from 'akar';
</script>
<template>
<ASliderRoot :default-value="[25, 75]">
<ASliderTrack>
<ASliderRange />
</ASliderTrack>
<ASliderThumb />
<ASliderThumb />
</ASliderRoot>
</template>
Define step size
Use the step prop to increase the stepping interval.
// index.vue
<script setup>
import { ASliderRange, ASliderRoot, ASliderThumb, ASliderTrack } from 'akar';
</script>
<template>
<ASliderRoot
:default-value="[50]"
:step="10"
>
<ASliderTrack>
<ASliderRange />
</ASliderTrack>
<ASliderThumb />
</ASliderRoot>
</template>
Prevent thumb overlap
Use minStepsBetweenThumbs to avoid thumbs with equal values.
// index.vue
<script setup>
import { ASliderRange, ASliderRoot, ASliderThumb, ASliderTrack } from 'akar';
</script>
<template>
<ASliderRoot
:default-value="[25, 75]"
:step="10"
:min-steps-between-thumbs="1"
>
<ASliderTrack>
<ASliderRange />
</ASliderTrack>
<ASliderThumb />
<ASliderThumb />
</ASliderRoot>
</template>
Accessibility
Adheres to the ASlider WAI-ARIA design pattern.
Keyboard Interactions
| Key | Description |
|---|---|
ArrowRight | Increases the value by the |
ArrowLeft | Decreases the value by the |
ArrowUp | Increases the value by the |
ArrowDown | Decreases the value by the |
PageUp | Increases the value by a larger |
PageDown | Decreases the value by a larger |
Shift + ArrowUp | Increases the value by a larger |
Shift + ArrowDown | Decreases the value by a larger |
Home | Sets the value to its minimum. |
End | Sets the value to its maximum. |
Inverted sliders
When the slider is inverted, some controls are inverted as well, depending on the orientation.
- When the slider is
horizontal(the default), ArrowRight, ArrowLeft, Home, and End are inverted. - When the slider is
vertical, ArrowUp, ArrowDown, PageUp, PageDown, Shift + ArrowUp, and Shift + ArrowDown are inverted.
Custom APIs
Create your own API by abstracting the primitive parts into your own component.
Abstract all parts
This example abstracts all of the ASlider parts so it can be used as a self closing element.
Usage
<script setup lang="ts">
import { ASlider } from './your-slider';
</script>
<template>
<ASlider :default-value="[25]" />
</template>
Implementation
// your-slider.ts
export { default as ASlider } from 'ASlider.vue';
<!-- ASlider.vue -->
<script setup lang="ts">
import type { ASliderRootEmits, ASliderRootProps } from 'akar';
import { ASliderRange, ASliderRoot, ASliderThumb, ASliderTrack, useForwardPropsEmits } from 'akar';
const props = defineProps<ASliderRootProps>();
const emits = defineEmits<ASliderRootEmits>();
const forward = useForwardPropsEmits(props, emits);
</script>
<template>
<ASliderRoot
v-slot="{ modelValue }"
v-bind="forward"
>
<ASliderTrack>
<ASliderRange />
</ASliderTrack>
<ASliderThumb
v-for="(_, i) in modelValue"
:key="i"
/>
</ASliderRoot>
</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:
<ASliderRoot
@mousedown="() => { console.log('onMouseDown') }"
@mouseup="() => { console.log('onMouseUp') }"
>
…
</ASliderRoot>
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.).