<script setup lang="ts">
import { ref } from 'vue'
import { Slider } from '@hina-ui/vue'
const volume = ref(60)
</script>
<template>
<Slider v-model="volume" aria-label="Volume" class="w-64" />
</template>
Usage
import { Slider } from '@hina-ui/vue'
The slider picks a number between min and max, bound with v-model. The value updates continuously while dragging, and commit fires once on release. Attributes it does not declare land on the thumb, so name it with aria-label or aria-labelledby. Width belongs to the layout; the slider fills its container.
Value: 40
<script setup lang="ts">
import { ref } from 'vue'
import { Slider, Stack, Text } from '@hina-ui/vue'
const brightness = ref(40)
</script>
<template>
<Stack gap="sm" class="w-64">
<Slider v-model="brightness" aria-label="Brightness" />
<Text size="sm" tone="muted">Value: {{ brightness }}</Text>
</Stack>
</template>
Examples
Range and step
min, max and step set the range and granularity; the arrow keys move by step too.
<script setup lang="ts">
import { ref } from 'vue'
import { Slider } from '@hina-ui/vue'
const rating = ref(3.5)
</script>
<template>
<Slider v-model="rating" :min="1" :max="5" :step="0.5" aria-label="Rating" class="w-64" />
</template>
Direction
dir accepts ltr or rtl and takes precedence over Reka’s global direction configuration. Without either setting, it inherits dir from the surrounding elements. Direction changes update the track, thumbs, marks and labels together.
In RTL, the minimum is on the right and the maximum on the left. ArrowRight decreases the value, ArrowLeft increases it, and Home / End still move to the minimum / maximum.
LTR
RTL
<script setup lang="ts">
import { ref } from 'vue'
import { Slider, Stack, Text } from '@hina-ui/vue'
const value = ref(25)
const marks = [0, 25, 50, 75, 100].map(value => ({ value, label: String(value) }))
</script>
<template>
<Stack gap="lg" class="w-72 max-w-full">
<Stack v-for="dir in ['ltr', 'rtl'] as const" :key="dir" gap="xs">
<Text size="sm" tone="muted">{{ dir.toUpperCase() }}</Text>
<Slider
v-model="value"
:dir="dir"
:marks="marks"
:step="5"
:aria-label="'Value (' + dir.toUpperCase() + ')'"
/>
</Stack>
</Stack>
</template>
Marks
marks places ticks on the track; a mark with a label shows its text underneath.
<script setup lang="ts">
import { ref } from 'vue'
import { Slider } from '@hina-ui/vue'
const speed = ref(50)
const marks = [
{ value: 0, label: 'Slow' },
{ value: 25 },
{ value: 50, label: 'Normal' },
{ value: 75 },
{ value: 100, label: 'Fast' },
]
</script>
<template>
<Slider v-model="speed" :step="25" :marks="marks" aria-label="Page turn speed" class="w-64" />
</template>
Value label
The value label is a Tooltip with the thumb as its trigger: it follows the thumb and flips to the other side near the viewport edge. By default the current value shows on hover, focus and while dragging; label="always" keeps it visible and label="none" removes it. format customises the text, which defaults to the number formatted for the current language. Like the IconButton hint it needs a TooltipProvider in the app, and stays hidden without one.
<script setup lang="ts">
import { ref } from 'vue'
import { Slider, Stack } from '@hina-ui/vue'
const quality = ref(80)
const opacity = ref(30)
</script>
<template>
<Stack gap="lg" class="w-64 pt-6">
<Slider
v-model="quality"
label="always"
:format="(v: number) => `${v}%`"
aria-label="Image quality"
/>
<Slider v-model="opacity" label="none" aria-label="Overlay opacity" />
</Stack>
</template>
Sizes
size is sm, md or lg; the track is 20, 24 or 28 pixels tall and the thumb 12, 16 or 20, the same as Switch.
<script setup lang="ts">
import { Slider, Stack } from '@hina-ui/vue'
</script>
<template>
<Stack gap="sm" class="w-64">
<Slider size="sm" :model-value="30" aria-label="Small" />
<Slider size="md" :model-value="50" aria-label="Medium" />
<Slider size="lg" :model-value="70" aria-label="Large" />
</Stack>
</template>
States
disabled disables the whole slider.
<script setup lang="ts">
import { Slider } from '@hina-ui/vue'
</script>
<template>
<Slider disabled :model-value="45" aria-label="Disabled" class="w-64" />
</template>
Commit on release
commit fires only when a drag or a key press ends, which suits expensive work such as sending a request.
While dragging: 20, on release: 20
<script setup lang="ts">
import { ref } from 'vue'
import { Slider, Stack, Text } from '@hina-ui/vue'
const value = ref(20)
const committed = ref(20)
</script>
<template>
<Stack gap="sm" class="w-64">
<Slider v-model="value" aria-label="Threshold" @commit="committed = $event" />
<Text size="sm" tone="muted">While dragging: {{ value }}, on release: {{ committed }}</Text>
</Stack>
</template>
In a form
Inside a FormField the label is linked to the thumb through aria-labelledby, and the description and error message are rendered by the field; validation rules and submission belong to the Form.
<script setup lang="ts">
import { reactive, ref } from 'vue'
import * as v from 'valibot'
import { Button, Form, FormField, Slider, Text } from '@hina-ui/vue'
const schema = v.object({
quality: v.pipe(v.number(), v.minValue(60, 'Quality must be at least 60')),
})
const values = reactive({ quality: 40 })
const saved = ref('')
async function save(data: unknown) {
await new Promise(resolve => setTimeout(resolve, 600))
saved.value = JSON.stringify(data)
}
</script>
<template>
<Form v-slot="{ submitting }" :values="values" :rules="schema" class="w-80" @submit="save">
<FormField
name="quality"
label="Export quality"
description="Below 60 the image degrades visibly"
>
<Slider v-model="values.quality" :min="0" :max="100" :step="5" />
</FormField>
<Button type="submit" :loading="submitting" class="self-start">Export</Button>
<Text v-if="saved" tone="muted" size="sm">Exported: {{ saved }}</Text>
</Form>
</template>
Behaviour
- Clicking anywhere on the track jumps the thumb there and starts dragging; while dragging the thumb and the fill follow the pointer without a transition, and jumps from clicks or keys are eased.
- Tab lands on the thumb; the left and right arrows move by
step, PageUp / PageDown move by a larger step, Home / End jump to the ends. - Hovering anywhere on the slider inks the thumb and fades the value label in; the ink deepens while dragging.
Accessibility
- The thumb is
role="slider"witharia-valuenow,aria-valuemin,aria-valuemaxandaria-orientation. - Name the thumb with
aria-labeloraria-labelledby; marks are hidden from screen readers, which readaria-valuenow; while shown, the value label is the thumb’saria-describedby.
API
Props
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue | number | — | The current value, min when unbound |
min | number | 0 | Minimum |
max | number | 100 | Maximum |
step | number | 1 | Step |
dir | 'ltr' | 'rtl' | — | Direction; inherited when omitted |
marks | Array<{ value: number; label?: string }> | — | Ticks |
label | 'auto' | 'always' | 'none' | 'auto' | How the value label shows |
format | (value: number) => string | — | Text of the value label, locale-formatted by default |
size | 'sm' | 'md' | 'lg' | 'md' | Size |
disabled | boolean | false | Whether the slider is disabled |
class | string | — | Classes appended to the root element |
Events
| Event | Payload | Description |
|---|---|---|
update:modelValue | value: number | The value changed, continuously while dragging |
commit | value: number | A drag or a key press ended |