輸入元件

雙端範圍滑桿

雙 handle 選取數值區間,附即時數值氣泡,適合價格篩選。

以兩個可拖曳的 handle 選取數值區間,中段軌道高亮顯示選取範圍,拖曳或聚焦時上方浮現即時數值氣泡,內建防交錯與完整鍵盤操作,常用於價格篩選、時間區段等場景。

把這個效果裝進你的專案

在專案資料夾執行指令,CLI 會加入原始碼與依賴套件,再依下方使用範例引用元件。

npx webberui@latest add range-dual-slider

還沒有專案,或安裝遇到問題?看快速開始 →

Range Dual Slider 是用 React、Tailwind CSS 與 Motion 做的雙滑桿區間選擇(range slider、雙向滑桿、價格區間篩選)元件:兩個 handle 各自控制下限與上限,數值對齊 step,拖曳或鍵盤聚焦時上方會浮出數值氣泡,並保證兩端不會交錯。

Playground

即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。

0
100
1
1
編輯中的呼叫預設屬性
<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型別預設值說明
minnumber0區間最小值
maxnumber100區間最大值
stepnumber1步進值,拖曳與鍵盤操作都會對齊到 step 網格
value[number, number]—受控值:[低值, 高值]
defaultValue[number, number][min, max]非受控模式的初始值
onChange(value: [number, number]) => void—區間變動時回呼(拖曳過程中即時觸發)
format(value: number) => stringString數值格式化函式,套用於氣泡與 aria-valuetext
minGapnumberstep兩個 handle 之間允許的最小間距,防止交錯
labels[string, string]["最小值", "最大值"]兩個 handle 的無障礙標籤
classNamestring—透傳到最外層容器

可及性

  • 兩個 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。