Demos
How to import
import { Button, InputNumber, Slider } from '@fibrous/semi-ui-solid';
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-solid';
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 { createSignal } from 'solid-js';
import { InputNumber, Slider } from '@fibrous/semi-ui-solid';
export default function SliderDemo() {
const [value, setValue] = createSignal(10);
const getSliderValue = (newValue?: number | string | number[]) => {
if (isNaN(Number(newValue))) {
return;
}
setValue(Number(newValue));
};
return (
<div>
<div style={{ width: '320px', 'margin-right': '15px' }}>
<Slider aria-label="数值" step={1} value={value()} onChange={getSliderValue} />
</div>
<InputNumber aria-label="数值" onChange={getSliderValue} style={{ width: '100px' }} 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-solid';
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. Mark labels accept only strings or numbers; custom nodes go through renderMark(value, label), which is called once for the label and once for the tooltip when tooltipOnMark is on.
Code
import { Slider } from '@fibrous/semi-ui-solid';
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 { createSignal } from 'solid-js';
import { Slider } from '@fibrous/semi-ui-solid';
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]}%)`,
};
};
export default function SliderDemo() {
const [value, setValue] = createSignal([20, 60]);
const changeValue = (newValue?: number | number[]) => {
if (Array.isArray(newValue)) {
setValue(newValue);
}
};
return <Slider aria-label="Value range" range min={range[0]} max={range[1]} onChange={changeValue} railStyle={getRailStyle(value())} value={value()} />;
}Controlled Component
You can use value along with onChange property if you want to use Slider as a controlled component. With only value and no write-back in onChange, dragging, clicking and arrow keys only fire onChange and the handle does not move.
Code
import { createSignal } from 'solid-js';
import { Button, Slider } from '@fibrous/semi-ui-solid';
export default function SliderDemo() {
const [value, setValue] = createSignal(10);
const changeValue = () => {
setValue(value() + 10);
};
return (
<div>
<Button onClick={changeValue} style={{ 'margin-right': '20px' }}>
点击改变value值
</Button>
<br />
<br />
<Slider aria-label="受控滑块" value={value()} />
</div>
);
}Vertical
Code
import { Slider } from '@fibrous/semi-ui-solid';
const boxStyle = {
height: '300px',
'margin-left': '30px',
'margin-top': '10px',
'padding-right': '30px',
display: 'inline-block',
};
const marks = { 20: '20°C', 40: '40°C' };
export default function SliderDemo() {
return (
<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" vertical range defaultValue={[20, 60]} />
</div>
<div style={boxStyle}>
<Slider aria-label="Reversed vertical range slider" vertical verticalReverse range defaultValue={[20, 60]} />
</div>
<div style={boxStyle}>
<Slider aria-label="Vertical range slider with marks" vertical range marks={marks} step={10} defaultValue={[20, 60]} />
</div>
<div style={boxStyle}>
<Slider aria-label="Reversed vertical range slider with marks" vertical verticalReverse range marks={marks} step={10} defaultValue={[20, 60]} />
</div>
</div>
);
}Handle with dot
Code
import { Slider } from '@fibrous/semi-ui-solid';
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 adds its start or end text. | string | - |
| aria-valuetext | A user-friendly name for the current value. | string | - |
| autofocus | Focuses the first handle on mount. | boolean | - |
| 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. | Type(value: number, index?: number) => string | - |
| handleDot | Dot on the handle: one object for a single handle, an array with one per handle for a range. | HandleDot | HandleDot[] | - |
| included | Takes effect when marks is not empty. true makes the marks nested, false makes them independent. | boolean | true |
| marks | Marks. Keys are numbers in the closed range [min, max], values are strings or numbers; custom nodes go through renderMark. | TypeRecord<number, string | number> | - |
| 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 | - |
| onBlur | Fires when focus leaves both handles, attached to .semi-slider-wrapper. Moving focus between the two handles does not fire it. | TypeJSX.EventHandlerUnion<HTMLDivElement, FocusEvent> | - |
| 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. | TypeJSX.EventHandlerUnion<HTMLDivElement, FocusEvent> | - |
| onKeyDown | Key handler on the handle's input, called before the slider handles the key. Calling preventDefault in it stops the slider from handling a value key. | TypeJSX.EventHandlerUnion<HTMLInputElement, KeyboardEvent> | - |
| 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(event: PointerEvent | KeyboardEvent) => void | - |
| railStyle | Style of the slider rail, an object keyed by CSS property names or a string. | JSX.CSSProperties | string | - |
| range | Whether both ends can be dragged. | boolean | false |
| renderMark | Renders a mark label, called with (value, label); with tooltipOnMark it is called once for the label and once for the tooltip. Defaults to the label. | Type(value: number, label: string | number) => JSX.Element | - |
| 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 |
| tipFormatter | Format 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) => any) | null | Type(value: number) => value |
| 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 |
class, style, ref and other native attributes (such as data-*) land on .semi-slider-wrapper, which also receives onFocus, onBlur, onMouseEnter and onMouseLeave. id (on the first handle only), name, 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.
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 | 滚动条轨道文本行高倍数 |