雙端範圍滑桿
雙 handle 選取數值區間,附即時數值氣泡,適合價格篩選。
以兩個可拖曳的 handle 選取數值區間,中段軌道高亮顯示選取範圍,拖曳或聚焦時上方浮現即時數值氣泡,內建防交錯與完整鍵盤操作,常用於價格篩選、時間區段等場景。
把這個效果裝進你的專案
在專案資料夾執行指令,CLI 會加入原始碼與依賴套件,再依下方使用範例引用元件。
npx webberui@latest add range-dual-sliderRange Dual Slider 是用 React、Tailwind CSS 與 Motion 做的雙滑桿區間選擇(range slider、雙向滑桿、價格區間篩選)元件:兩個 handle 各自控制下限與上限,數值對齊 step,拖曳或鍵盤聚焦時上方會浮出數值氣泡,並保證兩端不會交錯。
Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
<RangeDualSlider/>
完整範例包含預覽的排版與資料。先安裝元件,再把範例檔貼入你的專案。
適用情境
- 電商列表頁的價格區間篩選
- 租屋、買房網站的坪數、租金或屋齡範圍
- 機票、訂房的出發時段或預算範圍
- 後台報表依數值區間(金額、分數、年齡)篩選資料
安裝
npx webberui@latest add https://webberui.com/r/range-dual-slider.json或在 components.json 設定 registries 後,改用 @webberui/range-dual-slider 安裝。
使用
import { RangeDualSlider } from "@/components/ui/range-dual-slider";
const [range, setRange] = React.useState<[number, number]>([1500, 10000]);
<RangeDualSlider
min={0}
max={20000}
step={100}
value={range}
onChange={setRange}
format={(v) => `NT$${v}`}
/>範例
價格篩選:放開手再查詢
onChange 在拖曳過程中會連續觸發,直接打 API 會送出大量請求。先用 state 即時更新畫面,再延遲送出查詢。
"use client";
import * as React from "react";
import { RangeDualSlider } from "@/components/ui/range-dual-slider";
const twd = new Intl.NumberFormat("zh-TW", {
style: "currency",
currency: "TWD",
maximumFractionDigits: 0,
});
export function PriceFilter({ onSearch }: { onSearch: (r: [number, number]) => void }) {
const [range, setRange] = React.useState<[number, number]>([500, 5000]);
React.useEffect(() => {
const id = setTimeout(() => onSearch(range), 300);
return () => clearTimeout(id);
}, [range, onSearch]);
return (
<RangeDualSlider
min={0}
max={10000}
step={100}
minGap={500}
value={range}
onChange={setRange}
format={(v) => twd.format(v)}
labels={["最低價格", "最高價格"]}
/>
);
}minGap={500} 讓兩個 handle 至少相差 500,labels 會成為兩個 handle 的 aria-label。
搭配表單送出
元件本身不會輸出 <input>,要跟原生表單或 Server Action 一起送出時,自行加上隱藏欄位。
const [area, setArea] = React.useState<[number, number]>([15, 40]);
<form action="/search">
<RangeDualSlider
min={5}
max={100}
defaultValue={[15, 40]}
onChange={setArea}
format={(v) => `${v} 坪`}
labels={["最小坪數", "最大坪數"]}
/>
<input type="hidden" name="areaMin" value={area[0]} />
<input type="hidden" name="areaMax" value={area[1]} />
<button type="submit">搜尋物件</button>
</form>小數步進
step 可以是小數,元件會依 step 的小數位數修正浮點誤差,例如評分篩選。
<RangeDualSlider
min={0}
max={5}
step={0.5}
defaultValue={[3.5, 5]}
format={(v) => `${v.toFixed(1)} 星`}
labels={["最低評分", "最高評分"]}
/>Props
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
min | number | 0 | 區間最小值 |
max | number | 100 | 區間最大值 |
step | number | 1 | 步進值,拖曳與鍵盤操作都會對齊到 step 網格 |
value | [number, number] | — | 受控值:[低值, 高值] |
defaultValue | [number, number] | [min, max] | 非受控模式的初始值 |
onChange | (value: [number, number]) => void | — | 區間變動時回呼(拖曳過程中即時觸發) |
format | (value: number) => string | String | 數值格式化函式,套用於氣泡與 aria-valuetext |
minGap | number | step | 兩個 handle 之間允許的最小間距,防止交錯 |
labels | [string, string] | ["最小值", "最大值"] | 兩個 handle 的無障礙標籤 |
className | string | — | 透傳到最外層容器 |
可及性
- 兩個 handle 皆為
role="slider",帶有aria-valuemin/aria-valuemax/aria-valuenow,且上下限會隨另一個 handle 的位置動態收斂 aria-valuetext使用format後的可讀文字(例如「NT$1,500」),輔助科技朗讀更自然- 完整鍵盤支援:方向鍵單步調整、
PageUp/PageDown十倍步進、Home/End跳至端點 - 鍵盤聚焦時同樣顯示數值氣泡,並有清晰的 focus ring
- 使用者系統開啟「減少動態效果」時,氣泡與 handle 縮放改為即時顯示,不做彈簧動畫
常見問題
受控與非受控有什麼差別?
傳入 value 時以外部狀態為準,需要搭配 onChange 更新;只傳 defaultValue(或都不傳,預設 [min, max])時元件自己管理狀態,onChange 仍會通知變動。
onChange 什麼時候觸發?
拖曳過程中只要數值對齊到新的 step 就會觸發,鍵盤調整也一樣。元件沒有另外提供「放開時」的回呼,需要減少請求時請自行 debounce。
支援鍵盤和螢幕閱讀器嗎?
支援。兩個 handle 都是 role="slider",方向鍵單步、PageUp/PageDown 十倍步進、Home/End 跳到端點;aria-valuetext 使用 format 後的文字。
可以改顏色嗎?
軌道、選取區間與 handle 的顏色寫在原始碼的 Tailwind class 裡(預設 neutral 色系並支援深色模式),className 只套用在最外層容器。要換品牌色時,直接修改原始碼中對應的 class。
可以在 Next.js App Router 使用嗎?
可以。原始碼已經有 "use client",在 Server Component 頁面直接 import 即可;需要 useState 的受控用法要寫在 Client Component 裡。依賴是 motion、clsx、tailwind-merge。