Input · Slider

Slider

Selector to quickly select a number or range of values using drag interaction, more intuitive than InputNumber

Demos

How to import

<script setup lang="ts">
    import { Slider } from '@fibrous/semi-ui-vue';
</script>

Basic Usage

Basic slider. When range is true, both ends can be dragged. When disabled is true, the slider is unavailable.

默认
0100


范围
0100


禁用
0100
Code
<script setup lang="ts">
    import { Slider } from '@fibrous/semi-ui-vue';
</script>

<template>
    <div>
        <div>
            <div>默认</div>
            <Slider aria-label="默认滑动输入条" showBoundary />
        </div>
        <br />
        <br />
        <div>
            <div>范围</div>
            <Slider aria-label="范围滑动输入条" :defaultValue="[20, 60]" range />
        </div>
        <br />
        <br />
        <div>
            <div>禁用</div>
            <Slider aria-label="禁用滑动输入条" :defaultValue="40" disabled />
        </div>
    </div>
</template>

With Input

Synchronize slider with input value.

0100
Code
<script setup lang="ts">
    import { ref } from 'vue';
    import { InputNumber, Slider } from '@fibrous/semi-ui-vue';

    const value = ref(10);

    const getSliderValue = (newValue: number | string | number[]) => {
        if (isNaN(Number(newValue))) {
            return;
        }
        value.value = Number(newValue);
    };
</script>

<template>
    <div>
        <div :style="{ width: '320px', marginRight: '15px' }">
            <Slider aria-label="数值" :modelValue="value" :step="1" @change="getSliderValue" />
        </div>
        <InputNumber aria-label="数值" :max="100" :min="0" :modelValue="value" :style="{ width: '100px' }" @change="getSliderValue" />
    </div>
</template>

Tooltip

Use tipFormatter to format the Tooltip content, or pass null to hide the Tooltip. getAriaValueText gives the current value a user-friendly name, which matters to screen reader users.

Use the #tip slot when the tooltip needs node content; its scoped value is the result of tipFormatter.

0100


0100
Code
<script setup lang="ts">
    import { Slider } from '@fibrous/semi-ui-vue';

    const formatPercent = (value: number) => `${value}%`;
</script>

<template>
    <div>
        <Slider aria-label="带提示的滑块" :getAriaValueText="formatPercent" :tipFormatter="formatPercent" />
        <br />
        <br />
        <Slider aria-label="不带提示的滑块" :tipFormatter="null" />
    </div>
</template>

With Tag

Use marks to label measures on sliders, and modelValue / defaultValue to set the handle position. Use the #mark slot when a mark label needs node content.

step=10
01020304050100
0100


step=0.1
0.10.20.30.40.5
01


Marks
20°C40°C
0100


Included
20°C40°C
0100
Code
<script setup lang="ts">
    import { Slider } from '@fibrous/semi-ui-vue';

    const stepTenMarks = { 0: '0', 10: '10', 20: '20', 30: '30', 40: '40', 50: '50', 100: '100' };
    const stepTenthMarks = { 0.1: '0.1', 0.2: '0.2', 0.3: '0.3', 0.4: '0.4', 0.5: '0.5' };
    const celsiusMarks = { 20: '20°C', 40: '40°C' };
    const formatCelsius = (value: number) => `${value}°C`;
</script>

<template>
    <div>
        <div id="slider-marks-step-10">step=10</div>
        <Slider aria-labelledby="slider-marks-step-10" :defaultValue="[10, 100]" :marks="stepTenMarks" range :step="10" />
        <br />
        <br />
        <div id="slider-marks-step-01">step=0.1</div>
        <Slider aria-labelledby="slider-marks-step-01" :defaultValue="[0.1, 0.5]" :marks="stepTenthMarks" :max="1" :min="0" range :step="0.1" />
        <br />
        <br />
        <div id="slider-marks-marks">Marks</div>
        <Slider aria-labelledby="slider-marks-marks" :defaultValue="[0, 100]" :getAriaValueText="formatCelsius" :marks="celsiusMarks" range :tipFormatter="formatCelsius" />
        <br />
        <br />
        <div id="slider-marks-included">Included</div>
        <Slider aria-labelledby="slider-marks-included" :defaultValue="[0, 100]" :getAriaValueText="formatCelsius" :included="false" :marks="celsiusMarks" range :tipFormatter="formatCelsius" />
    </div>
</template>

Segmented Background

To create a slider with segmented background, you could use CSS property linear-gradient for railStyle along with @change to change background dynamically.

10100
Code
<script setup lang="ts">
    import { computed, ref } from 'vue';
    import { Slider } from '@fibrous/semi-ui-vue';

    const range = [10, 100];

    const getRailStyle = (value: number[]) => {
        // transparent lets the middle segment show the track color
        const color = ['var(--semi-color-danger)', 'transparent', 'var(--semi-color-success)'];
        const gradientPos = value.map((val) => Number(((val - range[0]) / (range[1] - range[0])).toFixed(2)) * 100);
        return {
            background: `linear-gradient(to right, ${color[0]} ${gradientPos[0]}%, ${color[1]} ${gradientPos[0]}%, ${color[1]} ${gradientPos[1]}%, ${color[2]} ${gradientPos[1]}%)`,
        };
    };

    const value = ref([20, 60]);

    const changeValue = (newValue: number | number[]) => {
        if (Array.isArray(newValue)) {
            value.value = newValue;
        }
    };

    const railStyle = computed(() => getRailStyle(value.value));
</script>

<template>
    <Slider aria-label="Value range" :max="range[1]" :min="range[0]" :modelValue="value" :railStyle="railStyle" range @change="changeValue" />
</template>

Controlled Component

Use modelValue with v-model if you want to use Slider as a controlled component. With only :modelValue and no write-back, dragging, clicking and arrow keys only emit change and the handle does not move.



0100
Code
<script setup lang="ts">
    import { ref } from 'vue';
    import { Button, Slider } from '@fibrous/semi-ui-vue';

    const value = ref(10);

    const changeValue = () => {
        value.value += 10;
    };
</script>

<template>
    <div>
        <Button :style="{ marginRight: '20px' }" @click="changeValue">点击改变value值</Button>
        <br />
        <br />
        <Slider aria-label="受控滑块" :modelValue="value" />
    </div>
</template>

Vertical

0100
0100
0100
0100
20°C40°C
0100
20°C40°C
0100
Code
<script setup lang="ts">
    import { Slider } from '@fibrous/semi-ui-vue';

    const boxStyle = {
        height: '300px',
        marginLeft: '30px',
        marginTop: '10px',
        paddingRight: '30px',
        display: 'inline-block',
    };
    const marks = { 20: '20°C', 40: '40°C' };
</script>

<template>
    <div>
        <div :style="boxStyle">
            <Slider aria-label="Vertical slider" vertical />
        </div>
        <div :style="boxStyle">
            <Slider aria-label="Reversed vertical slider" vertical verticalReverse />
        </div>
        <div :style="boxStyle">
            <Slider aria-label="Vertical range slider" :defaultValue="[20, 60]" range vertical />
        </div>
        <div :style="boxStyle">
            <Slider aria-label="Reversed vertical range slider" :defaultValue="[20, 60]" range vertical verticalReverse />
        </div>
        <div :style="boxStyle">
            <Slider aria-label="Vertical range slider with marks" :defaultValue="[20, 60]" :marks="marks" range :step="10" vertical />
        </div>
        <div :style="boxStyle">
            <Slider aria-label="Reversed vertical range slider with marks" :defaultValue="[20, 60]" :marks="marks" range :step="10" vertical verticalReverse />
        </div>
    </div>
</template>

Handle with dot

Default
0100


Range
0100
Code
<script setup lang="ts">
    import { Slider } from '@fibrous/semi-ui-vue';
</script>

<template>
    <div>
        <div>
            <div id="slider-handle-dot-default">Default</div>
            <Slider aria-labelledby="slider-handle-dot-default" :handleDot="{ size: '4px', color: 'blue' }" showBoundary />
        </div>
        <br />
        <br />
        <div>
            <div id="slider-handle-dot-range">Range</div>
            <Slider
                aria-labelledby="slider-handle-dot-range"
                :defaultValue="[20, 60]"
                :handleDot="[
                    { size: '4px', color: 'blue' },
                    { size: '4px', color: 'pink' },
                ]"
                range
            />
        </div>
    </div>
</template>

API Reference

Props

PropertyDescriptionTypeDefault
modelValueCurrent value, used with v-model. When given without a write-back, dragging, clicking and arrow keys only notify and the handle does not move. Back to undefined it returns to defaultValue, or min without one, and afterChange does not fire.number | number[]undefined
defaultValueInitial value when uncontrolled.number | number[]semiGlobalDefault("Slider", "defaultValue")
disabledWhether the slider is disabled.booleansemiDefault("Slider", "disabled", false)
includedTakes effect when marks is not empty. true makes the marks nested, false makes them independent.booleansemiDefault("Slider", "included", true)
marksMarks. Keys are numbers in the closed range [min, max], values are strings or numbers; use the mark slot for node content.TypeRecord<number, string | number>semiGlobalDefault("Slider", "marks")
maxMaximum value.numbersemiDefault("Slider", "max", 100)
minMinimum value.numbersemiDefault("Slider", "min", 0)
rangeWhether both ends can be dragged.booleansemiDefault("Slider", "range", false)
stepStep, counted from min.numbersemiDefault("Slider", "step", 1)
tipFormatterFormat of the tooltip, called once per handle with that handle's value. Shows the current value by default; null hides the tooltip.Type((value: number) => string | number) | nullundefined
tooltipVisibleWhether the tooltip is always visible.booleansemiGlobalDefault("Slider", "tooltipVisible")
tooltipOnMarkWhether the marks on the rail have a tooltip.booleansemiDefault("Slider", "tooltipOnMark", false)
showMarkLabelWhether to show the mark labels.booleansemiDefault("Slider", "showMarkLabel", true)
showArrowWhether the tooltip has an arrow.booleansemiDefault("Slider", "showArrow", true)
showBoundaryWhether to show the maximum and minimum on hover.booleansemiDefault("Slider", "showBoundary", false)
verticalWhether the slider is vertical.booleansemiDefault("Slider", "vertical", false)
verticalReverseReverses the vertical direction, so the larger value is on top.booleansemiDefault("Slider", "verticalReverse", false)
railStyleStyle of the slider rail.CSSPropertiessemiGlobalDefault("Slider", "railStyle")
handleDotDot on the handle: one object for a single handle, an array with one per handle for a range.HandleDot | HandleDot[]semiGlobalDefault("Slider", "handleDot")
idApplied to the first handle's input, so a label for focuses that handle.stringundefined
ariaLabelAccessible name of the handle. In range mode, the locale's start and end texts are appended for the two handles.stringsemiGlobalDefault("Slider", "ariaLabel")
ariaLabelledbyThe id of the labelling element, used as the handle's accessible name. In range mode each handle adds its start or end text.stringsemiGlobalDefault("Slider", "ariaLabelledby")
ariaValuetextA user-friendly name for the current value.stringsemiGlobalDefault("Slider", "ariaValuetext")
getAriaValueTextProvides a user-friendly name for the current value, called with the handle's value and its index.Type((value: number, index?: number | undefined) => string)undefined

Events

EventDescriptionCallback parameters
update:modelValueEmitted when the value changes, for v-model to write back.[value: number | number[]]
changeEmitted when the value changes, with the current value.[value: number | number[]]
afterChangeEmitted after a change is complete, with the current value. Not emitted when a controlled value goes back to undefined.[value: number | number[]]
mouseUpEmitted when the pointer (mouse, touch, pen) dragging a handle is released or cancelled, and when a value key is released.[event: KeyboardEvent | PointerEvent]
focusEmitted when any handle receives focus.[event: FocusEvent]
blurEmitted when focus leaves the whole Slider; moving between the two handles does not emit it.[event: FocusEvent]

Slots

SlotDescriptionScope parameters
markCustom mark label, also used for the tooltipOnMark tooltip. Scoped with the mark's value and label.SliderMarkSlotProps
tipCustom handle tooltip content. value is the result of tipFormatter and handle is min or max.SliderTipSlotProps

Exposes

NameDescriptionType
None

class, style and other native attributes land on .semi-slider-wrapper through attrs. aria-describedby, aria-invalid, aria-errormessage, name and @keydown land on each handle's native input; tabindex and aria-required are not rendered. In range mode both inputs share name and submit two values in order.

The $el of a component template ref is the outer .semi-slider element in horizontal mode and .semi-slider-wrapper in vertical mode.

RTL/LTR

  • Slider supports RTL for horizontal direction. You can enable it by setting direction="rtl" via ConfigProvider.
  • In RTL mode, the minimum value of the horizontal slider is on the right side and the maximum value is on the left side. The drag direction is reversed compared to LTR.
  • Vertical direction (vertical) is not affected by RTL.
  • In RTL mode, keyboard arrow keys behavior is mirrored: Left Arrow increases the value, Right Arrow decreases the value.

Accessibility

ARIA

  • Each handle holds a visually hidden native <input type="range">, which is the real focus and form control (implicit role slider); the visible handle only draws the thumb and takes the drag. Query the control with getByRole('slider') or input[type="range"], not .semi-slider-handle.
  • The input's value, min and max give assistive technology the current, minimum and maximum values. In range mode both inputs span the whole slider, and a handle stops where the other one is.
  • If the slider is vertically oriented, the input has aria-orientation="vertical" and a writing-mode: vertical-lr layout.
  • If the value is not user-friendly, e.g., the day of the week is represented by a number, support setting API aria-valuetext property to a string that makes the slider value understandable, e.g., "Monday". And you can use API getAriaValueText(value, index) to specify aria-valuetext.
  • Supporting API aria-label aria-labelledby to specify Slider label, or a <label for> pointing at id: the id lands on the first handle's input, and clicking the label focuses that handle.
  • When disabled, the locale text no longer overrides the handle's name: the native disabled expresses the state, and the name given by aria-label, aria-labelledby or <label for> stays. With no naming at all, a disabled handle has no name, just like an enabled one.
  • In range mode each handle has its own name: the locale's "Start" or "End" is appended to aria-label (e.g. Price Start, Price End); with aria-labelledby, each handle is named by that label followed by "Start" or "End"; with neither, the name is just "Start" or "End". The wrapper has no role and carries no aria-label.
  • Inside Form, the injected id, aria-labelledby, aria-describedby, aria-invalid and aria-errormessage land on the input; aria-required is not rendered because the slider role does not support it.
  • With a name, Slider submits with a native <form>: a single value submits one entry, and in range mode both inputs share the name and submit two entries in order (FormData.getAll(name) gives [min, max]). Like other native controls, a disabled Slider is not submitted.

Keyboard and Focus

  • The slider handles arrow keys itself, consistently across browsers; the native range input handles Home, End, Page Up and Page Down. A handle can get the focus; keyboard focus draws the focus outline on the visible handle and shows the prompt information of the current handle. Pressing a handle with a pointer also moves focus to it, without the focus outline.
  • onKeyDown (onKeydown in Vue) is bound on the handle's input and is called for every key before the slider handles it. Value keys (arrow keys, Home, End, Page Up, Page Down) do not bubble to ancestors by default; calling preventDefault in the callback intercepts the key and the slider no longer handles it.
  • When the user uses the range API, you can use Tab and Shift + Tab to switch the focus of the left and right sliders.
  • Arrow keys follow the direction the handle moves on screen: horizontally Right Arrow and Up Arrow increase the value, Left Arrow and Down Arrow decrease it (left and right mirrored in RTL); a vertical slider grows from top to bottom, so Down Arrow increases and Up Arrow decreases; with verticalReverse it grows from bottom to top, so Up Arrow increases and Down Arrow decreases; on a vertical slider Right Arrow always increases and Left Arrow always decreases.
  • If you want the slider to change more than the step size, Page Up and Page Down change the value by 10% of the range, rounded to the step:
    • Windows users: Page Up for increasing, Page Down for decreasing;
    • Mac users:Fn + Up Arrow for increasing, Fn + Down Arrow for decreasing;
    • When the user uses the range property, the Page Up key of the previous slider is only supported until it meets the next slider, and then using the Page Up key on the previous slider will not respond. The same is true for the latter slider. After encountering, there is no response to the Page Down key.
  • To move the slider to the minimum value of the slider:
    • Windows users: Home;
    • Mac users: Fn + left arrow;
    • When the user uses the range property, the Home(Fn + left arrow) button of the latter slider only supports until it meets the previous slider, and the Home(Fn + left arrow) button is unresponsive after the overlap.
  • To move the slider to the maximum value of the slider:
    • Windows users: End;
    • Mac users: Fn + right arrow;
    • When the user uses the range property, the End(Fn + right arrow) key of the previous slider is only supported until it meets the next slider, and the End(Fn + right arrow) key is unresponsive after the overlap.

Design Tokens

Focus Style Variables

The Slider component has two focus-related design tokens, each corresponding to different interaction scenarios:

Token NameCSS VariableTrigger ScenarioDescription
Handle outline - focus--semi-color-primary-light-activeKeyboard navigation (Tab focus)Drawn on the visible handle while its input matches :focus-visible (.semi-slider-handle:has(> input:focus-visible)), so only on keyboard focus
Handle border color - active--semi-color-focus-borderMouse click/dragUses .semi-slider-handle-clicked class, displays border during mouse interaction

If you want to modify the focus style of the handle in theme configuration, please note that these two variables correspond to different interaction scenarios.

These are the component's internal Sass variables and cannot be overridden. To customize styles, override the --semi-* CSS variables (see the Design Token page) or write unlayered CSS rules.
Sass variableValueUsage
$transition_duration-slider_handle-bgvar(--semi-transition_duration-none)滑动条圆形按钮-背景色-动画持续时间
$transition_function-slider_handle-bgvar(--semi-transition_function-easeIn)滑动条圆形按钮-背景色-过渡曲线
$transition_delay-slider_handle-bgvar(--semi-transition_delay-none)滑动条圆形按钮-背景色-延迟时间
$transform_scale-slider_handlevar(--semi-transform_scale-small)滑动条圆形按钮-放大
$color-slider-text-defaultvar(--semi-color-text-0)滑动条文本颜色 - 默认
$color-slider_dot-bg-defaultvar(--semi-color-white)滑动条圆形刻度点颜色 - 默认态
$color-slider_dot-border-activevar(--semi-color-white)滑动条圆形刻度点描边颜色 - 按下态
$color-slider_dot-bordervar(--semi-color-white)滑动条圆形刻度点描边颜色 - 默认态
$color-slider_handle-bg-defaultvar(--semi-color-white)滑动条圆形按钮颜色 - 默认态
$color-slider_handle-bg-hovervar(--semi-color-white)滑动条圆形按钮颜色 - 悬停态
$color-slider_handle-border-focusvar(--semi-color-focus-border)滑动条圆形描边颜色 - 激活态
$color-slider_handle_disabled-border-hovervar(--semi-color-white)禁用滑动条圆形描边颜色 - 悬停态
$color-slider_handle_disabled-bordervar(--semi-color-border)禁用滑动条圆形描边颜色 - 默认态
$color-slider_mark-text-defaultvar(--semi-color-text-2)滑动条刻度文字颜色
$color-slider_rail-bg-defaultvar(--semi-color-fill-0)滑动条轨道颜色 - 未填充
$color-slider_railrgba(0, 0, 0, 0.65)
$color-slider_track-bg-defaultvar(--semi-color-primary)滑动条轨道颜色 - 已填充
$color-slider_track_disabled-bgvar(--semi-color-primary-disabled)禁用滑动条轨道颜色 - 已填充
$color-slider_handle-focus$color-focus-ring圆形按钮轮廓 - 聚焦
$color-slider_handle_dotvar(--semi-color-primary)圆形按钮内部圆点颜色
$spacing-slider-paddingX13px滑动条整体水平内边距
$spacing-slider-paddingY0px滑动条整体垂直内边距
$spacing-slider-margin0px滑动条整体外边距
$spacing-slider_rail-margin0px滑动条轨道外边距
$spacing-slider_rail-padding0px滑动条轨道内边距
$spacing-slider_rail-top14px滑动条未填充轨道顶部距离
$spacing-slider_handle-marginTop4px滑动条圆形按钮顶部外边距
$spacing-slider_track-top14px滑动条已填充轨道顶部距离
$spacing-slider_tooltip-top-40px滑动条工具提示顶部距离
$spacing-slider_dot-top14px滑动条圆形刻度点顶部距离
$spacing-slider_marks-top23px滑动条刻度标签顶部距离
$spacing-slider_marks-left0px滑动条刻度标签左侧距离
$spacing-slider_boundary-top30px
$spacing-slider_boundary_min-left0px
$spacing-slider_boundary_max-right0px
$spacing-slider_vertical_marks-marginTop-30px垂直滑动条刻度标签顶部外边距
$spacing-slider_vertical_marks-marginLeft29px垂直滑动条刻度标签左侧外边距
$spacing-slider_vertical_marks-reverse-marginLeft-26px垂直滑动条刻度标签左侧外边距(标签在左侧时)
$spacing-slider_vertical_rail-top0px垂直滑动条轨道顶部距离
$spacing-slider_vertical_handle-marginTop0px垂直滑动条原型按钮顶部外边距
$spacing-slider_vertical_handle-marginLeft-10px垂直滑动条原型按钮左侧外边距
$spacing-slider_handle-translateY0px水平滑动条原型按钮垂直偏移量
$spacing-slider_vertical_handle-translateX0px垂直滑动条原型按钮水平偏移量
$spacing-slider_dot-translateX0px水平滑动条圆形刻度点水平偏移量
$spacing-slider_vertical_dot-translateY0px垂直滑动条圆形刻度点垂直偏移量
$radius-slider_railvar(--semi-border-radius-small)滚动条未填充轨道圆角
$radius-slider_trackvar(--semi-border-radius-small)滚动条已填充轨道圆角
$height-slider_wrapper32px滚动条容器整体高度
$height-slider_vertical_wrapper4px垂直滚动条整体宽度
$height-slider_rail4px滚动条未填充轨道高度
$width-slider_handle24px滚动条圆形按钮宽度
$width-slider_handle_clicked1px滚动条圆形按钮按下后描边宽度
$height-slider_track4px滚动条已填充轨道高度
$width-slider_dot4px滚动条圆形刻度点宽度
$width-slider_handle_border_disabled1px禁用滚动条圆形按钮按下后描边宽度
$width-slider_handle-focus$width-focus-ring圆形按钮轮廓 - 聚焦
$width-slider_handle_dot4px圆形按钮内部圆点宽度
$font-slider_rail-fontSize14px滚动条轨道文本字号
$font-slider_marks-fontSize14px滚动条刻度标签字号
$font-slider_rail-lineHeight1.5滚动条轨道文本行高倍数