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.
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.
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.
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.
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.
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.
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
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
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
| Property | Description | Type | Default |
|---|---|---|---|
| aria-label | Accessible name of the handle. In range mode, the locale's start and end texts are appended for the two handles. | string | - |
| aria-labelledby | The 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-valuetext | A user-friendly name for the current value. getAriaValueText takes precedence when set. | string | - |
| className | Class name of the .semi-slider-wrapper element. | string | - |
| defaultValue | Initial value when uncontrolled. Defaults to min. | number | number[] | - |
| disabled | Whether the slider is disabled. | boolean | false |
| getAriaValueText | Provides 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 | - |
| handleDot | Dot on the handle: one object for a single handle, an array with one per handle in range mode. | HandleDot | HandleDot[] | - |
| id | Set on the first handle's input, so a <label htmlFor> pointing at it focuses the slider when clicked. | string | - |
| included | Takes effect when marks is not an empty object. true makes the values nested, false makes them independent. | boolean | true |
| marks | Marks. 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> | - |
| max | Maximum value. | number | 100 |
| min | Minimum value. | number | 0 |
| name | Field 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 | - |
| onAfterChange | Fires after a change is complete, with the current value. Not fired when a controlled value goes back to undefined. | Type(value: SliderProps['value']) => void | - |
| onChange | Fires when the value changes, with the current value. | Type(value: SliderProps['value']) => void | - |
| onFocus | Fires when either handle gets focus, attached to .semi-slider-wrapper. | TypeReact.FocusEventHandler<HTMLDivElement> | - |
| onMouseUp | Fires 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 | - |
| railStyle | Style of the slider rail. | React.CSSProperties | - |
| range | Whether both ends can be dragged. | boolean | false |
| ref | Reference to the .semi-slider-wrapper element. | TypeReact.Ref<HTMLDivElement> | - |
| showArrow | Whether the tooltip has an arrow. | boolean | true |
| showBoundary | Whether to show the maximum and minimum on hover. | boolean | false |
| showMarkLabel | Whether to show the mark labels. | boolean | true |
| step | Step, counted from min. | number | 1 |
| style | Style of the .semi-slider-wrapper element. | React.CSSProperties | - |
| tipFormatter | Format of the tooltip, called once per handle with that handle's value. Pass null to hide the tooltip. | Type((value: number) => any) | null | Type(value: number) => value |
| title | The title attribute set on the .semi-slider-wrapper element. | string | - |
| tooltipOnMark | Whether the marks on the rail have a tooltip. | boolean | false |
| tooltipVisible | Whether the tooltip is always visible. | boolean | - |
| value | Current 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[] | - |
| vertical | Whether the slider is vertical. | boolean | false |
| verticalReverse | Reverses the vertical direction, so the larger value is on top. | boolean | false |
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 Arrowincreases the value,Right Arrowdecreases the value.
Accessibility
ARIA
- Each handle holds a visually hidden native
<input type="range">, which is the real focus and form control (implicit roleslider); the visible handle only draws the thumb and takes the drag. Query the control withgetByRole('slider')orinput[type="range"], not.semi-slider-handle. - The input's
value,minandmaxgive assistive technology the current, minimum and maximum values. Inrangemode 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 awriting-mode: vertical-lrlayout. - If the value is not user-friendly, e.g., the day of the week is represented by a number, support setting API
aria-valuetextproperty to a string that makes the slider value understandable, e.g., "Monday". And you can use APIgetAriaValueText(value, index)to specifyaria-valuetext. - Supporting API
aria-labelaria-labelledbyto specify Slider label, or a<label for>pointing atid: theidlands 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 nativedisabledexpresses the state, and the name given byaria-label,aria-labelledbyor<label for>stays. With no naming at all, a disabled handle has no name, just like an enabled one. - In
rangemode each handle has its own name: the locale's "Start" or "End" is appended toaria-label(e.g.Price Start,Price End); witharia-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 noaria-label. - Inside Form, the injected
id,aria-labelledby,aria-describedby,aria-invalidandaria-errormessageland on the input;aria-requiredis 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 inrangemode both inputs share thenameand 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 UpandPage 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(onKeydownin 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; callingpreventDefaultin the callback intercepts the key and the slider no longer handles it.- When the user uses the
rangeAPI, you can useTabandShift+Tabto switch the focus of the left and right sliders. - Arrow keys follow the direction the handle moves on screen: horizontally
Right ArrowandUp Arrowincrease the value,Left ArrowandDown Arrowdecrease it (left and right mirrored in RTL); a vertical slider grows from top to bottom, soDown Arrowincreases andUp Arrowdecreases; withverticalReverseit grows from bottom to top, soUp Arrowincreases andDown Arrowdecreases; on a vertical sliderRight Arrowalways increases andLeft Arrowalways decreases. - If you want the slider to change more than the step size,
Page UpandPage Downchange the value by 10% of the range, rounded to the step:- Windows users:
Page Upfor increasing,Page Downfor decreasing; - Mac users:
Fn+Up Arrowfor increasing,Fn+Down Arrowfor decreasing; - When the user uses the
rangeproperty, 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.
- Windows users:
- To move the slider to the minimum value of the slider:
- Windows users:
Home; - Mac users:
Fn+left arrow; - When the user uses the
rangeproperty, theHome(Fn+left arrow) button of the latter slider only supports until it meets the previous slider, and theHome(Fn+left arrow) button is unresponsive after the overlap.
- Windows users:
- To move the slider to the maximum value of the slider:
- Windows users:
End; - Mac users:
Fn+right arrow; - When the user uses the
rangeproperty, theEnd(Fn+right arrow) key of the previous slider is only supported until it meets the next slider, and theEnd(Fn+right arrow) key is unresponsive after the overlap.
- Windows users:
Design Tokens
Focus Style Variables
The Slider component has two focus-related design tokens, each corresponding to different interaction scenarios:
| Token Name | CSS Variable | Trigger Scenario | Description |
|---|---|---|---|
| Handle outline - focus | --semi-color-primary-light-active | Keyboard 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-border | Mouse click/drag | Uses .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.
| Sass variable | Value | Usage |
|---|---|---|
| $transition_duration-slider_handle-bg | var(--semi-transition_duration-none) | 滑动条圆形按钮-背景色-动画持续时间 |
| $transition_function-slider_handle-bg | var(--semi-transition_function-easeIn) | 滑动条圆形按钮-背景色-过渡曲线 |
| $transition_delay-slider_handle-bg | var(--semi-transition_delay-none) | 滑动条圆形按钮-背景色-延迟时间 |
| $transform_scale-slider_handle | var(--semi-transform_scale-small) | 滑动条圆形按钮-放大 |
| $color-slider-text-default | var(--semi-color-text-0) | 滑动条文本颜色 - 默认 |
| $color-slider_dot-bg-default | var(--semi-color-white) | 滑动条圆形刻度点颜色 - 默认态 |
| $color-slider_dot-border-active | var(--semi-color-white) | 滑动条圆形刻度点描边颜色 - 按下态 |
| $color-slider_dot-border | var(--semi-color-white) | 滑动条圆形刻度点描边颜色 - 默认态 |
| $color-slider_handle-bg-default | var(--semi-color-white) | 滑动条圆形按钮颜色 - 默认态 |
| $color-slider_handle-bg-hover | var(--semi-color-white) | 滑动条圆形按钮颜色 - 悬停态 |
| $color-slider_handle-border-focus | var(--semi-color-focus-border) | 滑动条圆形描边颜色 - 激活态 |
| $color-slider_handle_disabled-border-hover | var(--semi-color-white) | 禁用滑动条圆形描边颜色 - 悬停态 |
| $color-slider_handle_disabled-border | var(--semi-color-border) | 禁用滑动条圆形描边颜色 - 默认态 |
| $color-slider_mark-text-default | var(--semi-color-text-2) | 滑动条刻度文字颜色 |
| $color-slider_rail-bg-default | var(--semi-color-fill-0) | 滑动条轨道颜色 - 未填充 |
| $color-slider_rail | rgba(0, 0, 0, 0.65) | |
| $color-slider_track-bg-default | var(--semi-color-primary) | 滑动条轨道颜色 - 已填充 |
| $color-slider_track_disabled-bg | var(--semi-color-primary-disabled) | 禁用滑动条轨道颜色 - 已填充 |
| $color-slider_handle-focus | $color-focus-ring | 圆形按钮轮廓 - 聚焦 |
| $color-slider_handle_dot | var(--semi-color-primary) | 圆形按钮内部圆点颜色 |
| $spacing-slider-paddingX | 13px | 滑动条整体水平内边距 |
| $spacing-slider-paddingY | 0px | 滑动条整体垂直内边距 |
| $spacing-slider-margin | 0px | 滑动条整体外边距 |
| $spacing-slider_rail-margin | 0px | 滑动条轨道外边距 |
| $spacing-slider_rail-padding | 0px | 滑动条轨道内边距 |
| $spacing-slider_rail-top | 14px | 滑动条未填充轨道顶部距离 |
| $spacing-slider_handle-marginTop | 4px | 滑动条圆形按钮顶部外边距 |
| $spacing-slider_track-top | 14px | 滑动条已填充轨道顶部距离 |
| $spacing-slider_tooltip-top | -40px | 滑动条工具提示顶部距离 |
| $spacing-slider_dot-top | 14px | 滑动条圆形刻度点顶部距离 |
| $spacing-slider_marks-top | 23px | 滑动条刻度标签顶部距离 |
| $spacing-slider_marks-left | 0px | 滑动条刻度标签左侧距离 |
| $spacing-slider_boundary-top | 30px | |
| $spacing-slider_boundary_min-left | 0px | |
| $spacing-slider_boundary_max-right | 0px | |
| $spacing-slider_vertical_marks-marginTop | -30px | 垂直滑动条刻度标签顶部外边距 |
| $spacing-slider_vertical_marks-marginLeft | 29px | 垂直滑动条刻度标签左侧外边距 |
| $spacing-slider_vertical_marks-reverse-marginLeft | -26px | 垂直滑动条刻度标签左侧外边距(标签在左侧时) |
| $spacing-slider_vertical_rail-top | 0px | 垂直滑动条轨道顶部距离 |
| $spacing-slider_vertical_handle-marginTop | 0px | 垂直滑动条原型按钮顶部外边距 |
| $spacing-slider_vertical_handle-marginLeft | -10px | 垂直滑动条原型按钮左侧外边距 |
| $spacing-slider_handle-translateY | 0px | 水平滑动条原型按钮垂直偏移量 |
| $spacing-slider_vertical_handle-translateX | 0px | 垂直滑动条原型按钮水平偏移量 |
| $spacing-slider_dot-translateX | 0px | 水平滑动条圆形刻度点水平偏移量 |
| $spacing-slider_vertical_dot-translateY | 0px | 垂直滑动条圆形刻度点垂直偏移量 |
| $radius-slider_rail | var(--semi-border-radius-small) | 滚动条未填充轨道圆角 |
| $radius-slider_track | var(--semi-border-radius-small) | 滚动条已填充轨道圆角 |
| $height-slider_wrapper | 32px | 滚动条容器整体高度 |
| $height-slider_vertical_wrapper | 4px | 垂直滚动条整体宽度 |
| $height-slider_rail | 4px | 滚动条未填充轨道高度 |
| $width-slider_handle | 24px | 滚动条圆形按钮宽度 |
| $width-slider_handle_clicked | 1px | 滚动条圆形按钮按下后描边宽度 |
| $height-slider_track | 4px | 滚动条已填充轨道高度 |
| $width-slider_dot | 4px | 滚动条圆形刻度点宽度 |
| $width-slider_handle_border_disabled | 1px | 禁用滚动条圆形按钮按下后描边宽度 |
| $width-slider_handle-focus | $width-focus-ring | 圆形按钮轮廓 - 聚焦 |
| $width-slider_handle_dot | 4px | 圆形按钮内部圆点宽度 |
| $font-slider_rail-fontSize | 14px | 滚动条轨道文本字号 |
| $font-slider_marks-fontSize | 14px | 滚动条刻度标签字号 |
| $font-slider_rail-lineHeight | 1.5 | 滚动条轨道文本行高倍数 |