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 lang="ts">
    import { Button, InputNumber, Slider } from '@fibrae/semi-ui-svelte';
</script>

Basic Usage

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

默认
0 100


范围
0 100


禁用
0 100
Code
<script lang="ts">
    import { Slider } from '@fibrae/semi-ui-svelte';
</script>

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

With Input

Synchronize slider with input value.

0 100
Code
<script lang="ts">
    import { InputNumber, Slider } from '@fibrae/semi-ui-svelte';

    let value = $state(10);

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

<div>
    <div style="width: 320px; margin-right: 15px">
        <Slider aria-label="数值" onChange={getSliderValue} step={1} {value} />
    </div>
    <InputNumber aria-label="数值" max={100} min={0} onChange={getSliderValue} style="width: 100px" {value} />
</div>

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 snippet when the tooltip needs node content; in its parameter { value, handle }, value is the result of tipFormatter and handle is min or max.

0 100


0 100
Code
<script lang="ts">
    import { Slider } from '@fibrae/semi-ui-svelte';

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

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

With Tag

Use marks to label measures on sliders, and value to set the handle position. Mark labels accept only strings or numbers; use the mark snippet, with the parameter { value, label }, when a label needs node content. It is rendered once for the label and once for the tooltip when tooltipOnMark is on.

step=10
01020304050100
0 100


step=0.1
0.10.20.30.40.5
0 1


Marks
20°C40°C
0 100


Included
20°C40°C
0 100
Code
<script lang="ts">
    import { Slider } from '@fibrae/semi-ui-svelte';

    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>

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

Segmented Background

To create a slider with segmented background, you could use CSS property linear-gradient for railStyle (a string) along with bind:value to change background dynamically.

10 100
Code
<script lang="ts">
    import { Slider } from '@fibrae/semi-ui-svelte';

    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]}%)`;
    };

    let value = $state<number | number[]>([20, 60]);

    const railStyle = $derived(getRailStyle(Array.isArray(value) ? value : [value, value]));
</script>

<Slider aria-label="Value range" bind:value max={range[1]} min={range[0]} {railStyle} range />

Controlled Component

The handle position is the Slider's value; keep it in sync with a $state variable through bind:value. To refuse or rewrite a drag result, use a function binding bind:value={() => value, (next) => { ... }}. A plain value without a binding is only the initial value, and a new value from the parent wins again.



0 100
Code
<script lang="ts">
    import { Button, Slider } from '@fibrae/semi-ui-svelte';

    let value = $state<number | number[]>(10);

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

<div>
    <Button style="margin-right: 20px" onclick={changeValue}>点击改变value值</Button>
    <br />
    <br />
    <Slider aria-label="受控滑块" bind:value />
</div>

Vertical

0 100
0 100
0 100
0 100
20°C40°C
0 100
20°C40°C
0 100
Code
<script lang="ts">
    import { Slider } from '@fibrae/semi-ui-svelte';

    const boxStyle = 'height: 300px; margin-left: 30px; margin-top: 10px; padding-right: 30px; display: inline-block';
    const marks = { 20: '20°C', 40: '40°C' };
</script>

<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" range value={[20, 60]} vertical />
    </div>
    <div style={boxStyle}>
        <Slider aria-label="Reversed vertical range slider" range value={[20, 60]} vertical verticalReverse />
    </div>
    <div style={boxStyle}>
        <Slider aria-label="Vertical range slider with marks" {marks} range step={10} value={[20, 60]} vertical />
    </div>
    <div style={boxStyle}>
        <Slider aria-label="Reversed vertical range slider with marks" {marks} range step={10} value={[20, 60]} vertical verticalReverse />
    </div>
</div>

Handle with dot

Default
0 100


Range
0 100
Code
<script lang="ts">
    import { Slider } from '@fibrae/semi-ui-svelte';
</script>

<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"
            handleDot={[
                { size: '4px', color: 'blue' },
                { size: '4px', color: 'pink' },
            ]}
            range
            value={[20, 60]}
        />
    </div>
</div>

API Reference

Props

PropertyDescriptionTypeDefault
aria-labelAccessible name of the handle. In range mode, the locale's start and end texts are appended for the two handles.string | null-
aria-labelledbyThe id of the labelling element, used as the handle's accessible name. In range mode each handle adds its start or end text.string | null-
aria-valuetextA user-friendly name for the current value.string | null-
disabledWhether the slider is disabled.booleanfalse
getAriaValueTextProvides a user-friendly name for the current value, called with the handle's value and its index.Type(value: number, index?: number) => string-
handleDotDot on the handle: one object for a single handle, an array with one per handle for a range.HandleDot | HandleDot[]-
idApplied to the first handle's input, so a label for focuses that handle.string-
includedTakes effect when marks is not empty. true makes the marks nested, false makes them independent.booleantrue
markSnippet for custom mark labels, also used for the tooltipOnMark tooltip. Its parameter is { value, label }, the mark's value and label.TypeSnippet<[SliderMarkSnippetProps]>-
marksMarks. Keys are numbers in the closed range [min, max], values are strings or numbers; use the mark snippet for node content.TypeRecord<number, string | number>-
maxMaximum value.number100
minMinimum value.number0
onAfterChangeCalled after a change is complete, with the current value. With a bound or one-way value it is called on every change in addition to onChange; without value, only onChange is called while dragging and this is called once on release.Type(value: number | number[]) => void-
onBlurCalled when focus leaves the whole Slider; moving between the two handles does not call it.Type(event: FocusEvent) => void-
onChangeCalled when the value changes, with the current value.Type(value: number | number[]) => void-
onFocusCalled when focus enters the Slider. It is the wrapper's focusin, so it is called each time focus moves between handles.Type(event: FocusEvent) => void-
onKeyDownCalled for keys on the handle inputs, before the Slider's own handling; preventDefault() keeps a value key from moving the handle.Type(event: KeyboardEvent) => void-
onMouseUpCalled when the pointer (mouse, touch, pen) dragging a handle is released or cancelled, and when a value key is released.Type(event: PointerEvent | KeyboardEvent) => void-
railStyleStyle of the slider rail. Accepts a string only.string-
rangeWhether both ends can be dragged.booleanfalse
showArrowWhether the tooltip has an arrow.booleantrue
showBoundaryWhether to show the maximum and minimum on hover.booleanfalse
showMarkLabelWhether to show the mark labels.booleantrue
stepStep, counted from min.number1
tipSnippet for custom handle tooltip content. Its parameter is { value, handle }; value is the result of tipFormatter and handle is min or max.TypeSnippet<[SliderTipSnippetProps]>-
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) | nullType(tipValue: number) => tipValue
tooltipOnMarkWhether the marks on the rail have a tooltip.booleanfalse
tooltipVisibleWhether the tooltip is always visible.boolean-
valueCurrent value; bind it with bind:value. bind:value={get, set} makes it fully controlled; a plain value is the initial value and a new value from the parent wins again; without it the Slider manages the value itself, starting at min.number | number[]-
verticalWhether the slider is vertical.booleanfalse
verticalReverseReverses the vertical direction, so the larger value is on top.booleanfalse

class, style (a string) and other native attributes (such as data-*) land on .semi-slider-wrapper; onFocus is its focusin, and onBlur is called only when focus leaves the whole Slider. id (on the first handle only), name, autofocus, aria-describedby, aria-invalid, aria-errormessage and onKeyDown land on each handle's native input. In range mode both inputs share name and submit two values in order. tabindex and aria-required are not rendered. value is bindable and there is no defaultValue.

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滚动条轨道文本行高倍数