HextaUI

Slider

ドラッグ、トラックのタップ、キー操作で値や範囲を選びます。ジャンプは滑らかに動き、値のバブルは任意で表示できます。

Volume
60
import { Slider, SliderLabel, SliderValue } from "@/components/ui/slider"

export function SliderDemo() {
  return (
    <Slider defaultValue={60} className="max-w-xs">
      <SliderLabel>Volume</SliderLabel>
      <SliderValue />
    </Slider>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/slider.json

コンポーネント、HextaUIのテーマトークン、依存するHextaUIコンポーネントを追加します。

import { Slider, SliderLabel, SliderValue } from "@/components/ui/slider"
<Slider defaultValue={[50]} max={100} step={1} aria-label="Volume" />

<Slider defaultValue={50}>
  <SliderLabel>Volume</SliderLabel>
  <SliderValue />
</Slider>

<Slider /> は値ごとにつまみを1つ描画するため、数値または1要素の配列なら単一のスライダー、2要素なら範囲指定になります。<SliderLabel /> や <SliderValue /> などの子要素は、トラックの上の1行に並びます。

ドラッグ中、つまみはポインターにぴったり追従します。トラックの押下、キー、制御された新しい値によって値がジャンプするときは、つまみと塗りは瞬間移動せず、180msで滑らかに移動します。

Slider
├── SliderLabel
└── SliderValue

サイズ

sm、default、lg は、つまみとトラックをまとめて拡大縮小します。

import { Slider } from "@/components/ui/slider"

export function SliderSizes() {
  return (
    <div className="flex w-full max-w-xs flex-col gap-8">
      <Slider size="sm" defaultValue={25} aria-label="Small" />
      <Slider defaultValue={50} aria-label="Default" />
      <Slider size="lg" defaultValue={75} aria-label="Large" />
    </div>
  )
}

範囲

範囲指定には2つの値を渡します。各つまみには getAriaLabel で名前を付け、minStepsBetweenValues で間隔を保ちます。draggableRange を指定すると、つまみの間のどこをドラッグしても両方が同時に動き、そこをタップした場合は最も近いつまみが動きます。

"use client"

import { Slider, SliderLabel, SliderValue } from "@/components/ui/slider"

export function SliderRange() {
  return (
    <Slider
      defaultValue={[200, 800]}
      max={1000}
      step={10}
      minStepsBetweenValues={10}
      draggableRange
      format={{ style: "currency", currency: "USD", maximumFractionDigits: 0 }}
      getAriaLabel={(index) =>
        index === 0 ? "Minimum price" : "Maximum price"
      }
      className="max-w-xs"
    >
      <SliderLabel>Price</SliderLabel>
      <SliderValue />
    </Slider>
  )
}

値のバブル

showValue は、ドラッグ中またはキーボードでフォーカスされている間、つまみの上にフォーマットした値を表示します。バブルはトラックの上の空間にせり上がるので、ラベルの行の代わりに使います。

import { Slider } from "@/components/ui/slider"

export function SliderValueBubble() {
  return (
    <div className="flex w-full max-w-xs flex-col gap-10 pt-8">
      <Slider
        defaultValue={0.4}
        max={1}
        step={0.01}
        format={{ style: "percent" }}
        showValue
        aria-label="Opacity"
      />
      <Slider defaultValue={[30, 70]} showValue aria-label="Range" />
    </div>
  )
}

ステップとフォーマット

step は値をスナップさせ、largeStep は Page Up と Shift + 矢印のジャンプ量を設定し、format はスライダーが表示または読み上げるすべての数値をフォーマットします。

import { Slider, SliderLabel, SliderValue } from "@/components/ui/slider"

export function SliderSteps() {
  return (
    <Slider
      defaultValue={1}
      min={0.5}
      max={2}
      step={0.25}
      largeStep={0.5}
      format={{ minimumFractionDigits: 2 }}
      className="max-w-xs"
    >
      <SliderLabel>Playback speed</SliderLabel>
      <SliderValue />
    </Slider>
  )
}

制御

onValueChange は動くたびに、onValueCommitted は離したときに一度だけ発火します。保存するのはこのタイミングです。外部から値を設定した場合も滑らかに動きます。

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import { Slider, SliderLabel, SliderValue } from "@/components/ui/slider"

export function SliderControlled() {
  const [value, setValue] = React.useState(40)
  const [saved, setSaved] = React.useState(40)

  return (
    <div className="flex w-full max-w-xs flex-col gap-4">
      <Slider
        value={value}
        onValueChange={setValue}
        onValueCommitted={setSaved}
      >
        <SliderLabel>Brightness</SliderLabel>
        <SliderValue />
      </Slider>
      <div className="flex items-center justify-between gap-3">
        <span className="text-sm text-muted-foreground">Saved: {saved}</span>
        <div className="flex gap-2">
          <Button variant="outline" size="sm" onClick={() => setValue(0)}>
            Off
          </Button>
          <Button variant="outline" size="sm" onClick={() => setValue(100)}>
            Max
          </Button>
        </div>
      </div>
    </div>
  )
}

垂直

orientation="vertical" では、スライダーは親の高さを満たし、Up で値が増えます。

import { Slider } from "@/components/ui/slider"

const bands = [
  { label: "60 Hz", value: 70 },
  { label: "250 Hz", value: 45 },
  { label: "1 kHz", value: 55 },
  { label: "4 kHz", value: 80 },
  { label: "16 kHz", value: 35 },
]

export function SliderVertical() {
  return (
    <div className="flex h-48 gap-6">
      {bands.map((band) => (
        <div key={band.label} className="flex flex-col items-center gap-3">
          <Slider
            orientation="vertical"
            defaultValue={band.value}
            aria-label={band.label}
          />
          <span className="text-xs text-muted-foreground tabular-nums">
            {band.label}
          </span>
        </div>
      ))}
    </div>
  )
}

アイコンつき

flex の行でスライダーの横にアイコンを置きます。スライダーが残りの幅を占めます。

import { IconVolume, IconVolume3 } from "@tabler/icons-react"

import { Slider } from "@/components/ui/slider"

export function SliderIcons() {
  return (
    <div className="flex w-full max-w-xs items-center gap-3 text-muted-foreground">
      <IconVolume3 className="size-4 shrink-0" aria-hidden />
      <Slider defaultValue={50} aria-label="Volume" />
      <IconVolume className="size-4 shrink-0" aria-hidden />
    </div>
  )
}

無効

スライダー全体が薄くなり、ポインターとキーボードの入力を無視します。

import { Slider, SliderLabel, SliderValue } from "@/components/ui/slider"

export function SliderDisabled() {
  return (
    <Slider defaultValue={30} disabled className="max-w-xs">
      <SliderLabel>Bass boost</SliderLabel>
      <SliderValue />
    </Slider>
  )
}

長いラベル

長いラベルは折り返され、値は末尾に残ります。

import { Slider, SliderLabel, SliderValue } from "@/components/ui/slider"

export function SliderLongLabel() {
  return (
    <Slider
      defaultValue={[1200, 48000]}
      min={0}
      max={50000}
      step={100}
      format={{ style: "currency", currency: "USD", maximumFractionDigits: 0 }}
      className="max-w-xs"
    >
      <SliderLabel>
        Annual household income before taxes, including side projects
      </SliderLabel>
      <SliderValue />
    </Slider>
  )
}

右から左

右から左の親の内側では、塗りが右から始まり、矢印キーもそれに従います。読み手の数字で表示するには locale を渡します。

import { Slider, SliderLabel, SliderValue } from "@/components/ui/slider"

export function SliderRtl() {
  return (
    <div dir="rtl" className="flex w-full max-w-xs flex-col gap-8">
      <Slider defaultValue={65} locale="ar-EG">
        <SliderLabel>مستوى الصوت</SliderLabel>
        <SliderValue />
      </Slider>
      <Slider defaultValue={[20, 70]} locale="ar-EG" draggableRange>
        <SliderLabel>النطاق</SliderLabel>
        <SliderValue />
      </Slider>
    </div>
  )
}
キーアクション
Tab次のつまみにフォーカスを移します。
→↑値を1ステップ増やします。右から左では → で減ります。
←↓値を1ステップ減らします。右から左では ← で増えます。
Shift + ArrowPage UpPage DownlargeStep の分だけ動きます。
Home最小値を設定します。範囲指定では前のつまみの値を設定します。
End最大値を設定します。範囲指定では次のつまみの値を設定します。
  • 各つまみはネイティブの input type="range" を持つため、それぞれ独自の値、min、max を持つ slider です。
  • <SliderLabel /> はすべてのつまみに名前を付けます。ない場合でも、<Slider /> の aria-label はラッパーではなくつまみに渡されるため、スライダーが名前なしで残ることはありません。範囲指定では getAriaLabel でつまみを区別します。
  • <SliderValue /> と値のバブルは、変化のたびには読み上げられません。つまみがすでに値を伝えるためです。
  • つまみを押してもフォーカスリングは表示されませんが、矢印キーでは表示されます。視差効果の軽減が有効な場合、つまみは滑らかに動かずジャンプします。
  • タッチスクリーンでは、レイアウトを変えずにヒット領域が最低44pxまで広がります。

Base UI の slider 上に構築されています。すべてのパーツは、ラップしているプリミティブの props を受け付けます。

プロパティ型デフォルト
value制御される値。1項目につきつまみ1つ。
number | number[]–
defaultValue
number | number[]–
onValueChangeドラッグ中、変化のたびに発火します。
(value, eventDetails) => void–
onValueCommitted変化が終わったときに一度だけ発火します。
(value, eventDetails) => void–
min
number0
max
number100
step
number1
largeStep
number10
minStepsBetweenValues
number0
size
"sm" | "default" | "lg""default"
orientation
"horizontal" | "vertical""horizontal"
draggableRange範囲指定では、つまみの間をドラッグすると、間隔を保ったまま両方が一緒に動きます。
booleanfalse
showValueドラッグ中またはキーボードフォーカス中に、つまみの上に値を表示します。
booleanfalse
thumbAlignmentedge は、min と max でつまみをトラックの内側に収めます。
"edge" | "center" | "edge-client-only""edge"
thumbCollisionBehavior
"push" | "swap" | "none""push"
format
Intl.NumberFormatOptions–
locale
Intl.LocalesArgument"en-US"
aria-labelつまみに名前を付けます。
string–
getAriaLabel範囲指定の各つまみに名前を付けます。
(index: number) => string–
getAriaValueText
(formattedValue: string, value: number, index: number) => string–
name
string–
disabled
booleanfalse
className
string | (state) => string–
render
ReactElement | (props, state) => ReactElement<div>
属性説明
data-slot="slider"ルート。
data-sizesm、default、lg。
data-jumpつまみが新しい値へ滑らかに動く220msの間付きます。
data-range-dragging範囲全体をドラッグしている間付きます。
data-orientationhorizontal または vertical。
data-draggingポインターが押されている間付きます。
data-disabled無効のときに存在します。
data-invalidField の内側で無効(invalid)なときに付きます。
--slider-thumbつまみの直径。size によって設定されます。
--slider-trackトラックの太さ。size によって設定されます。
data-slot="slider-control"トラックの周りの押せる領域。
data-slot="slider-track"トラック。
data-slot="slider-range"開始位置、または最初のつまみから、最後のつまみまでの塗り。
data-slot="slider-thumb"各つまみ。data-index がその位置を示します。
data-activeフォーカスされている、またはドラッグ中のつまみに付きます。
data-slot="slider-thumb-value"showValue による値のバブル。

つまみに名前を付け、行を埋めるまで広がります。<div> を描画します。

プロパティ型デフォルト
render
ReactElement | (props, state) => ReactElement<div>
属性説明
data-slot="slider-label"ラベル。

フォーマットした値を表示します。範囲指定ではエンダッシュでつなぎます。<output> を描画します。

プロパティ型デフォルト
childrenカスタムテキスト。
(formattedValues: string[], values: number[]) => ReactNode–
render
ReactElement | (props, state) => ReactElement<output>
属性説明
data-slot="slider-value"値。
data-orientationhorizontal または vertical。
data-draggingポインターが押されている間付きます。
data-disabled無効のときに存在します。
data-invalidField の内側で無効(invalid)なときに付きます。

使用しているブロック

Slider の上に構築されるブロック。