Input · Slider

Slider

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

Demos

How to import

import { Slider } from '@fibrous/semi-ui-react';

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
import { Slider } from '@fibrous/semi-ui-react';

export default function SliderDemo() {
    return (
        <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>
    );
}

With Input

Synchronize slider with input value.

0100
Code
import { useState } from 'react';
import { Slider, InputNumber } from '@fibrous/semi-ui-react';

export default function SliderDemo() {
    const [value, setValue] = useState(10);

    const getSliderValue = (newValue?: number | string | number[]) => {
        if (isNaN(Number(newValue))) {
            return;
        }
        setValue(Number(newValue));
    };

    return (
        <div>
            <div style={{ width: 320, marginRight: 15 }}>
                <Slider aria-label="数值" step={1} value={value} onChange={getSliderValue} />
            </div>
            <InputNumber aria-label="数值" onChange={getSliderValue} style={{ width: 100 }} value={value} min={0} max={100} />
        </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.

0100


0100
Code
import { Slider } from '@fibrous/semi-ui-react';

export default function SliderDemo() {
    return (
        <div>
            <Slider aria-label="带提示的滑块" tipFormatter={(v) => `${v}%`} getAriaValueText={(v) => `${v}%`} />
            <br />
            <br />
            <Slider aria-label="不带提示的滑块" tipFormatter={null} />
        </div>
    );
}

With Tag

Use marks to label measures on sliders, and value / defaultValue to set the handle position.

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
import { Slider } from '@fibrous/semi-ui-react';

export default function SliderDemo() {
    return (
        <div>
            <div id="slider-marks-step-10">step=10</div>
            <Slider aria-labelledby="slider-marks-step-10" step={10} marks={{ 0: '0', 10: '10', 20: '20', 30: '30', 40: '40', 50: '50', 100: '100' }} defaultValue={[10, 100]} range />
            <br />
            <br />
            <div id="slider-marks-step-01">step=0.1</div>
            <Slider aria-labelledby="slider-marks-step-01" step={0.1} marks={{ 0.1: '0.1', 0.2: '0.2', 0.3: '0.3', 0.4: '0.4', 0.5: '0.5' }} min={0} max={1} defaultValue={[0.1, 0.5]} range />
            <br />
            <br />
            <div id="slider-marks-marks">Marks</div>
            <Slider aria-labelledby="slider-marks-marks" marks={{ 20: '20°C', 40: '40°C' }} defaultValue={[0, 100]} tipFormatter={(v) => `${v}°C`} range getAriaValueText={(value) => `${value}°C`} />
            <br />
            <br />
            <div id="slider-marks-included">Included</div>
            <Slider aria-labelledby="slider-marks-included" marks={{ 20: '20°C', 40: '40°C' }} included={false} defaultValue={[0, 100]} range tipFormatter={(v) => `${v}°C`} getAriaValueText={(value) => `${value}°C`} />
        </div>
    );
}

Segmented Background

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

10100
Code
import { useState, useMemo } from 'react';
import { Slider } from '@fibrous/semi-ui-react';

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);
    const style = {
        background: `linear-gradient(to right, ${color[0]} ${gradientPos[0]}%, ${color[1]} ${gradientPos[0]}%, ${color[1]} ${gradientPos[1]}%, ${color[2]} ${gradientPos[1]}%)`,
    };
    return style;
};

export default function SliderDemo() {
    const [value, setValue] = useState([20, 60]);

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

    const railStyle = useMemo(() => getRailStyle(value), [value]);

    return <Slider aria-label="Value range" range min={range[0]} max={range[1]} onChange={changeValue} railStyle={railStyle} value={value} />;
}

Controlled Component

You can use value along with onChange property if you want to use Slider as a controlled component.



0100
Code
import { useState } from 'react';
import { Slider, Button } from '@fibrous/semi-ui-react';

export default function SliderDemo() {
    const [value, setValue] = useState(10);

    const changeValue = () => {
        setValue(value + 10);
    };

    return (
        <div>
            <Button onClick={changeValue} style={{ marginRight: 20 }}>
                点击改变value值
            </Button>
            <br />
            <br />
            <Slider aria-label="受控滑块" value={value} />
        </div>
    );
}

Vertical

0100
0100
0100
0100
20°C40°C
0100
20°C40°C
0100
Code
import { Slider } from '@fibrous/semi-ui-react';

export default function SliderDemo() {
    return (
        <div>
            <div style={{ height: 300, marginLeft: 30, marginTop: 10, paddingRight: 30, display: 'inline-block' }}>
                <Slider aria-label="Vertical slider" vertical />
            </div>
            <div style={{ height: 300, marginLeft: 30, marginTop: 10, paddingRight: 30, display: 'inline-block' }}>
                <Slider aria-label="Reversed vertical slider" vertical verticalReverse />
            </div>
            <div style={{ height: 300, marginLeft: 30, marginTop: 10, paddingRight: 30, display: 'inline-block' }}>
                <Slider aria-label="Vertical range slider" vertical range defaultValue={[20, 60]} />
            </div>
            <div style={{ height: 300, marginLeft: 30, marginTop: 10, paddingRight: 30, display: 'inline-block' }}>
                <Slider aria-label="Reversed vertical range slider" vertical verticalReverse range defaultValue={[20, 60]} />
            </div>
            <div style={{ height: 300, marginLeft: 30, marginTop: 10, paddingRight: 30, display: 'inline-block' }}>
                <Slider aria-label="Vertical range slider with marks" vertical range marks={{ 20: '20°C', 40: '40°C' }} step={10} defaultValue={[20, 60]} />
            </div>
            <div style={{ height: 300, marginLeft: 30, marginTop: 10, paddingRight: 30, display: 'inline-block' }}>
                <Slider aria-label="Reversed vertical range slider with marks" vertical verticalReverse range marks={{ 20: '20°C', 40: '40°C' }} step={10} defaultValue={[20, 60]} />
            </div>
        </div>
    );
}

Handle with dot

Default
0100


Range
0100
Code
import { Slider } from '@fibrous/semi-ui-react';

export default function SliderDemo() {
    return (
        <div>
            <div>
                <div id="slider-handle-dot-default">Default</div>
                <Slider aria-labelledby="slider-handle-dot-default" showBoundary handleDot={{ size: '4px', color: 'blue' }} />
            </div>
            <br />
            <br />
            <div>
                <div id="slider-handle-dot-range">Range</div>
                <Slider
                    aria-labelledby="slider-handle-dot-range"
                    defaultValue={[20, 60]}
                    range
                    handleDot={[
                        { size: '4px', color: 'blue' },
                        { size: '4px', color: 'pink' },
                    ]}
                />
            </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-
aria-labelledbyThe id of the labelling element, used as the handle's accessible name. In range mode each handle's name is that label plus its start or end text.string-
aria-valuetextA user-friendly name for the current value. getAriaValueText takes precedence when set.string-
classNameClass name of the .semi-slider-wrapper element.string-
defaultValueInitial value when uncontrolled. Defaults to min.number | number[]-
disabledWhether the slider is disabled.booleanfalse
getAriaValueTextProvides a user-friendly name for the current value, called with the handle's value and its index. Takes precedence over aria-valuetext.Type(value: number, index?: number) => string-
handleDotDot on the handle: one object for a single handle, an array with one per handle in range mode.HandleDot | HandleDot[]-
idSet on the first handle's input, so a <label htmlFor> pointing at it focuses the slider when clicked.string-
includedTakes effect when marks is not an empty object. true makes the values nested, false makes them independent.booleantrue
marksMarks. Keys must be number values in the closed range [min, max]; values are any node, shown next to the mark and in its tooltip.TypeRecord<number, React.ReactNode>-
maxMaximum value.number100
minMinimum value.number0
nameField name submitted with a native form, set on each handle's input. In range mode both inputs share it and submit two values in order.string-
onAfterChangeFires after a change is complete, with the current value. Not fired when a controlled value goes back to undefined.Type(value: SliderProps['value']) => void-
onChangeFires when the value changes, with the current value.Type(value: SliderProps['value']) => void-
onFocusFires when either handle gets focus, attached to .semi-slider-wrapper.TypeReact.FocusEventHandler<HTMLDivElement>-
onMouseUpFires when the pointer (mouse, touch or pen) that dragged the handle is released or cancelled, and when a value key is released.Type(e: any) => void-
railStyleStyle of the slider rail.React.CSSProperties-
rangeWhether both ends can be dragged.booleanfalse
refReference to the .semi-slider-wrapper element.TypeReact.Ref<HTMLDivElement>-
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
styleStyle of the .semi-slider-wrapper element.React.CSSProperties-
tipFormatterFormat of the tooltip, called once per handle with that handle's value. Pass null to hide the tooltip.Type((value: number) => any) | nullType(value: number) => value
titleThe title attribute set on the .semi-slider-wrapper element.string-
tooltipOnMarkWhether the marks on the rail have a tooltip.booleanfalse
tooltipVisibleWhether the tooltip is always visible.boolean-
valueCurrent value, for controlled use. With value but no write-back in onChange, dragging, clicking and arrow keys only fire onChange and the handle does not move. Back to undefined it returns to defaultValue, or min without one, and onAfterChange does not fire.number | number[]-
verticalWhether the slider is vertical.booleanfalse
verticalReverseReverses the vertical direction, so the larger value is on top.booleanfalse

Other native attributes (such as data-*) land on .semi-slider-wrapper. aria-describedby, aria-invalid, aria-errormessage and onKeyDown land on each handle's native input, and autoFocus applies to the first handle's input. onBlur is attached to .semi-slider-wrapper and does not fire when focus moves between the two handles. tabIndex and aria-required are not rendered.

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