RangeSlider 范围滑块

在数值范围内拖出一段区间。

'use client'

import { useState } from 'react'
import { RangeSlider } from '@hina-ui/react'

export default function Demo() {
  const [price, setPrice] = useState<[number, number]>([120, 480])

  return (
    <RangeSlider
      value={price}
      onValueChange={setPrice}
      max={1000}
      step={10}
      aria-label="价格区间"
      className="w-64"
    />
  )
}
tsx

用法

import { RangeSlider } from '@hina-ui/react'
ts

范围滑块有两个拇指,value / onValueChange 绑定 [起点, 终点] 的二元数组,填充覆盖两个拇指之间的一段。轨道、拇指、取值标签与 Slider 相同。未声明的属性都会传给根元素,应当用 aria-label 或者 aria-labelledby 给整组命名;两个拇指各自带有「最小值」「最大值」的名称。

当前值:2010 至 2020

'use client'

import { useState } from 'react'
import { RangeSlider, Stack, Text } from '@hina-ui/react'

export default function Demo() {
  const [year, setYear] = useState<[number, number]>([2010, 2020])

  return (
    <Stack gap="sm" className="w-64">
      <RangeSlider value={year} onValueChange={setYear} min={1990} max={2026} aria-label="年份" />
      <Text size="sm" tone="muted">
        当前值:{year[0]} 至 {year[1]}
      </Text>
    </Stack>
  )
}
tsx

示例

方向

dir 支持 ltr 和 rtl,优先于 ConfigProvider 的全局方向配置;未配置时继承外层元素的 dir。切换方向会同步更新轨道、拇指、刻度点与刻度文字。

RTL 下最小值在右、最大值在左;右方向键减小数值,左方向键增大数值,Home / End 仍分别跳到最小值与最大值。

LTR

RTL

'use client'

import { useState } from 'react'
import { RangeSlider, Stack, Text } from '@hina-ui/react'

const marks = [0, 25, 50, 75, 100].map(value => ({ value, label: String(value) }))

export default function Demo() {
  const [value, setValue] = useState<[number, number]>([25, 75])

  return (
    <Stack gap="lg" className="w-72 max-w-full">
      {(['ltr', 'rtl'] as const).map(dir => (
        <Stack key={dir} gap="xs">
          <Text size="sm" tone="muted">
            {dir.toUpperCase()}
          </Text>
          <RangeSlider
            value={value}
            onValueChange={setValue}
            dir={dir}
            marks={marks}
            step={5}
            aria-label={'区间 (' + dir.toUpperCase() + ')'}
          />
        </Stack>
      ))}
    </Stack>
  )
}
tsx

刻度

marks 与 Slider 相同,step 决定拇指的落点。

'use client'

import { useState } from 'react'
import { RangeSlider } from '@hina-ui/react'

const marks = [
  { value: 0, label: '0' },
  { value: 25 },
  { value: 50, label: '50' },
  { value: 75 },
  { value: 100, label: '100' },
]

export default function Demo() {
  const [range, setRange] = useState<[number, number]>([25, 75])

  return (
    <RangeSlider
      value={range}
      onValueChange={setRange}
      step={25}
      marks={marks}
      aria-label="区间"
      className="w-64"
    />
  )
}
tsx

最小间隔

minSteps 规定两个拇指之间至少相隔的步数,拖动或者按键都不能越过。

两个拇指之间至少相隔四步

'use client'

import { useState } from 'react'
import { RangeSlider, Stack, Text } from '@hina-ui/react'

export default function Demo() {
  const [hours, setHours] = useState<[number, number]>([9, 18])

  return (
    <Stack gap="sm" className="w-64">
      <RangeSlider
        value={hours}
        onValueChange={setHours}
        max={24}
        minSteps={4}
        label="always"
        aria-label="营业时间"
      />
      <Text size="sm" tone="muted">
        两个拇指之间至少相隔四步
      </Text>
    </Stack>
  )
}
tsx

取值标签

每个拇指各有一个取值标签,label 与 format 的用法与 Slider 相同。

'use client'

import { useState } from 'react'
import { RangeSlider, Stack } from '@hina-ui/react'

export default function Demo() {
  const [discount, setDiscount] = useState<[number, number]>([20, 60])
  const [hidden, setHidden] = useState<[number, number]>([30, 70])

  return (
    <Stack gap="lg" className="w-64 pt-6">
      <RangeSlider
        value={discount}
        onValueChange={setDiscount}
        label="always"
        format={(v: number) => `${v}%`}
        aria-label="折扣区间"
      />
      <RangeSlider value={hidden} onValueChange={setHidden} label="none" aria-label="不显示标签" />
    </Stack>
  )
}
tsx

尺寸

import { RangeSlider, Stack } from '@hina-ui/react'

export default function Demo() {
  return (
    <Stack gap="sm" className="w-64">
      <RangeSlider size="sm" value={[20, 60]} aria-label="小号" />
      <RangeSlider size="md" value={[20, 60]} aria-label="中号" />
      <RangeSlider size="lg" value={[20, 60]} aria-label="大号" />
    </Stack>
  )
}
tsx

状态

import { RangeSlider } from '@hina-ui/react'

export default function Demo() {
  return <RangeSlider disabled value={[30, 70]} aria-label="已禁用" className="w-64" />
}
tsx

在表单中

放进 FormField 后,标签通过 aria-labelledby 关联到整个滑块组,说明与错误信息由字段渲染;校验规则与提交交给 Form。值是二元数组,区间宽度这类规则写在数组层。

拖动两端设定区间

'use client'

import { useState } from 'react'
import * as v from 'valibot'
import { Button, Form, FormField, RangeSlider, Text } from '@hina-ui/react'

const schema = v.object({
  budget: v.pipe(
    v.tuple([v.number(), v.number()]),
    v.check(([low, high]) => high - low >= 200, '预算区间至少相差 200 元'),
  ),
})

export default function Demo() {
  const [values, setValues] = useState({ budget: [300, 400] as [number, number] })
  const [saved, setSaved] = useState('')

  async function save(data: unknown) {
    await new Promise(resolve => setTimeout(resolve, 600))
    setSaved(JSON.stringify(data))
  }

  return (
    <Form values={values} rules={schema} className="w-80" onSubmit={save}>
      {({ submitting }) => (
        <>
          <FormField name="budget" label="预算" description="拖动两端设定区间">
            <RangeSlider
              value={values.budget}
              onValueChange={budget => setValues({ ...values, budget })}
              min={0}
              max={1000}
              step={50}
            />
          </FormField>
          <Button type="submit" loading={submitting} className="self-start">
            保存
          </Button>
          {saved && (
            <Text tone="muted" size="sm">
              已保存:{saved}
            </Text>
          )}
        </>
      )}
    </Form>
  )
}
tsx

行为

  • 点击轨道时,离点击处更近的拇指跳过去;拖动只移动被按住的拇指,两个拇指不会交叉。
  • 键盘 Tab 依次落在两个拇指上,方向键只移动持有焦点的那个。
  • 拖动过程中值持续更新,松手时另外调用一次 onCommit。

无障碍

  • 根元素是 role="group",通过 aria-label 或者 aria-labelledby 命名;两个拇指是 role="slider",名称取自语言包的「最小值」与「最大值」,并带 aria-valuenow、aria-valuemin、aria-valuemax。

API

Props

属性
类型
默认值
说明
value
[number, number]
[min, max]
起点与终点
min
number
0
最小值
max
number
100
最大值
step
number
1
步长
dir
'ltr' | 'rtl'
—
方向,未指定时继承
minSteps
number
0
两个拇指之间至少相隔的步数
marks
Array<{ value: number; label?: string }>
—
刻度
label
'auto' | 'always' | 'none'
'auto'
取值标签的显示方式
format
(value: number) => string
—
取值标签的文字
size
'sm' | 'md' | 'lg'
'md'
尺寸
disabled
boolean
false
是否禁用
className
string
—
追加至根元素的类名

回调

回调
参数
说明
onValueChange
value: [number, number]
值变化时,拖动中持续触发
onCommit
value: [number, number]
一次拖动或者按键结束时