Slider
カスタマイズできるスライダーのコンポーネント。
Source Style- React
- Svelte
- Vue
function App() { const [value, setValue] = useState(64) return ( <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', gap: 8, }} > <Slider.Root className={sliderTheme.root} value={value} min={0} max={100} onChange={(v) => setValue(v)} > <Slider.Track className={sliderTheme.track}> <Slider.Thumb className={sliderTheme.thumb} aria-label="Level" /> </Slider.Track> </Slider.Root> <span style={{ width: '2rem', }} > {value} </span> </div> ) }
/** The demo theme for `Slider`, as a CSS Module: one class per part, applied by* the caller. Copy this file into your project and edit it — it is a starting* point, not a dependency.** It says what the parts look like, not where they go: the component places* them itself.*//** The size the thumb is drawn at, and the room the slider leaves for it. Both* parts need the value and nothing inherits it, so it is composed into each* of them and stays written once.*/.thumbSize {--thumb-size: 22px;}.root {composes: thumbSize;display: inline-flex;flex-direction: column;margin: calc(var(--thumb-size) / 2);cursor: pointer;outline: 0;-webkit-tap-highlight-color: transparent;&[data-orientation='vertical'] {flex-direction: row;}&[data-readonly] {cursor: not-allowed;}}.thumb {composes: thumbSize;--color: oklch(59.5% 0.17548 266.373);width: var(--thumb-size);height: var(--thumb-size);background: var(--color);border-radius: 50%;outline: none;&[data-disabled] {background: oklch(50.462% 0.03307 270.021);}&:focus-within {box-shadow: 0px 0px 0px 3pxcolor-mix(in srgb, var(--color) 20%, transparent);}}.track {--active-color: oklch(69.212% 0.12903 267.633);--inactive-color: oklch(95% 0 0);--length: 140px;--thickness: 10px;/* Which way the fill runs. `--percent` is where the thumb sits on screen,so a flipped slider only swaps which side of it is the filled one. */--axis: to right;width: var(--length);height: var(--thickness);border-radius: calc(var(--thickness) / 2);/* The component sets `--percent` and paints nothing itself, so the fill isyours to change: a second colour stop, a gradient, an image. */background: linear-gradient(var(--axis),var(--active-color) var(--percent),var(--inactive-color) var(--percent));&[data-flipped] {background: linear-gradient(var(--axis),var(--inactive-color) var(--percent),var(--active-color) var(--percent));}&[data-orientation='horizontal'] {min-width: 50px;}&[data-orientation='vertical'] {--axis: to bottom;width: var(--thickness);height: var(--length);min-height: 50px;}&:hover {--active-color: oklch(64.276% 0.15244 266.932);--inactive-color: #e0e0e0;}&[data-disabled] {--active-color: oklch(75.327% 0.01237 264.547);--inactive-color: oklch(89.755% 0.0001 271.152);}}.marks {/* Space between the marks and the track. */--gap: 6px;display: block;margin-top: var(--gap);&[data-orientation='vertical'] {margin-top: 0;margin-left: var(--gap);}}.marksOption {/* Read by the mark and the label below, which is why they are set here. */--thickness: 1px;--length: 0.5rem;--gap: 2px;display: flex;color: oklch(25.197% 0.00003 271.152);flex-direction: column;justify-content: center;align-items: center;&[data-orientation='vertical'] {flex-direction: row;}}.mark {background-color: currentColor;width: var(--thickness);height: var(--length);margin-bottom: var(--gap);&[data-orientation='vertical'] {width: var(--length);height: var(--thickness);margin-bottom: 0;margin-right: var(--gap);}}.label {text-align: right;}/** Dark mode follows whatever marks the page — `.dark` and `[data-theme=dark]`* are what this site and Storybook set. Swap them for however your own app* says it, or read `prefers-color-scheme` instead.*/:global(.dark) .thumb,:global([data-theme='dark']) .thumb {--color: oklch(64.5% 0.17548 266.373);&:hover {--color: oklch(67.5% 0.17548 266.373);}}:global(.dark) .track,:global([data-theme='dark']) .track {--active-color: oklch(59.5% 0.17548 266.373);--inactive-color: oklch(41.7% 0 0);&:hover {--active-color: oklch(62.5% 0.17548 266.373);--inactive-color: oklch(44.7% 0 0);}&[data-disabled] {--active-color: oklch(75.327% 0.01237 264.547);--inactive-color: oklch(89.755% 0.0001 271.152);}}
<script lang="ts">
import { Slider } from '@tremolo-ui/svelte'
// Copy this file from the Styling page into your own project.
import sliderTheme from './Slider.module.css'
let value = $state(64)
</script>
<Slider.Root class={sliderTheme.root} bind:value min={0} max={100}>
<Slider.Track class={sliderTheme.track}>
<Slider.Thumb class={sliderTheme.thumb} aria-label="Level" />
</Slider.Track>
</Slider.Root>
<span>{value}</span>
<script setup lang="ts">
import { ref } from 'vue'
import { Slider, SliderThumb, SliderTrack } from '@tremolo-ui/vue'
// Copy this file from the Styling page into your own project.
import sliderTheme from './Slider.module.css'
const value = ref(64)
</script>
<template>
<Slider v-model="value" :class="sliderTheme.root" :min="0" :max="100">
<SliderTrack :class="sliderTheme.track">
<SliderThumb :class="sliderTheme.thumb" aria-label="Level" />
</SliderTrack>
</Slider>
<span>{{ value }}</span>
</template>
インポート
- React
- Svelte
- Vue
import { Slider } from '@tremolo-ui/react'
import { Slider } from '@tremolo-ui/svelte'
import {
Slider,
SliderMarks,
SliderMarksOption,
SliderThumb,
SliderTrack,
} from '@tremolo-ui/vue'
API リファレンス
各パートには、そのパート独自の props を載せています。これに加えて、描画する要素の属性(className や style など)も受け取ります。
Svelte / Vue でも props は同じで、書き方だけがそれぞれの流儀になります(className ではなく class、bind:value や v-model、中身は snippet やスロットなど)。Svelte と Vue を参照してください。
状態は data 属性として要素に乗っているので、セレクタ 1 つで届きます(スタイリングを参照)。属性はその状態が on の間だけ付きます。
Root
値と設定を持ち、中のパートに渡します。
| Prop | 型 | 既定値 |
|---|---|---|
value* | number | — |
min* | number | — |
max* | number | — |
step | number | 1 |
scale | Scale | linearScale |
orientation | "horizontal" | "vertical" | 'horizontal' |
reverse | boolean | false |
dragCursor | Cursor | 'pointer' |
wheel | ModifierValue<InputEventOption> | null | ['raw', 1] |
dragSensitivity | ModifierValue<number> | { default: 1, shift: 0.1 } |
keyboard | ModifierValue<InputEventOption> | null | { default: ['raw', 1], shift: ['raw', 0.1] } |
disabled | boolean | — |
readOnly | boolean | — |
onChange | (value: number) => void | — |
onChangeStart | (value: number, source: ChangeSource) => void | — |
onChangeEnd | (value: number, source: ChangeSource) => void | — |
changeEndDelay | number | 500 |
children* | ReactNode | — |
actionsRef | Ref<SliderMethods> | — |
すべての説明: SliderProps
| Data 属性 | 値 |
|---|---|
[data-orientation] | "horizontal" | "vertical" |
[data-disabled] | disabled のときに付きます |
[data-readonly] | readonly のときに付きます |
Track
値が動くバー。値の位置を --percent に書くので、テーマは値より下の部分を塗れます。Slider.Thumb はこの中に置きます。
| Prop | 型 | 既定値 |
|---|---|---|
length | string | number | — |
thickness | string | number | — |
activeColor | string | — |
inactiveColor | string | — |
すべての説明: SliderTrackProps
| カスタムプロパティ | 書き込む prop | 決めるもの |
|---|---|---|
--length | length | スライダーの向きに沿ったトラックの長さ。 |
--thickness | thickness | それと直交する方向のトラックの太さ。 |
--active-color | activeColor | min から値までの部分の色。 |
--inactive-color | inactiveColor | トラックの残りの部分の色。 |
--percent | コンポーネント | 値の位置(パーセント)。 |
--percent は CSS だけでは算出できない唯一の値で、トラックが自分では何も塗らない
理由でもあります。塗りはパッケージが描くものではなく、テーマのルールです。
.track {
background: linear-gradient(
var(--axis),
var(--active-color) var(--percent),
var(--inactive-color) var(--percent)
);
}
このルールを置き換えれば、色を足す、画像を敷く、値に応じて埋まる目盛りにするなど、 トラックは何にでもできます。
| Data 属性 | 値 |
|---|---|
[data-orientation] | "horizontal" | "vertical" |
[data-disabled] | disabled のときに付きます |
[data-flipped] | 値が向こう側の端から伸びるとき(縦向きと reverse のどちらか一方)に付きます |
Thumb
つまみ。中にある視覚的に隠れた range input が、フォーカスを受け取り ARIA を持ちます。
子はつまみの中に描かれます。つまみはどちらにしても 1 つの要素なので、子はつまみを置き換えるのではなく、その上に載る飾りです。つまみ自体の見た目は className と style で変えます。
| Prop | 型 | 既定値 |
|---|---|---|
color | string | — |
actionsRef | Ref<SliderThumbMethods> | — |
すべての説明: SliderThumbProps
| カスタムプロパティ | 書き込む prop | 決めるもの |
|---|---|---|
--color | color | つまみの色。 |
| Data 属性 | 値 |
|---|---|
[data-disabled] | disabled のときに付きます |
[data-readonly] | readonly のときに付きます |
Marks
トラックに沿って並ぶ目盛り。options か、目盛りごとに置いた子の Slider.MarksOption から作ります。options を設定している間、子は無視されます。
| Prop | 型 | 既定値 |
|---|---|---|
gap | string | number | — |
options | MarksOptions | — |
すべての説明: SliderMarksProps
| カスタムプロパティ | 書き込む prop | 決めるもの |
|---|---|---|
--gap | gap | 目盛りとトラックの間隔。 |
| Data 属性 | 値 |
|---|---|
[data-orientation] | "horizontal" | "vertical" |
MarksOption
value の位置に置く、1 つの目盛りとそのラベル。
| Prop | 型 | 既定値 |
|---|---|---|
value* | number | — |
mark | boolean | true |
label | string | number | null | — |
thickness | string | number | — |
length | string | number | — |
gap | string | number | — |
classes | { mark?: string; label?: string } | — |
styles | { mark?: CSSProperties; label?: CSSProperties } | — |
すべての説明: SliderMarksOptionProps
| カスタムプロパティ | 書き込む prop | 決めるもの |
|---|---|---|
--thickness | thickness | 目盛りの太さ。 |
--length | length | 目盛りの長さ。 |
--gap | gap | 目盛りとラベルの間隔。 |
ラベルの幅は styles.label または classes.label のクラスで指定します。
| Data 属性 | 値 |
|---|---|
[data-orientation] | "horizontal" | "vertical" |
中の目盛りにも [data-orientation] が付きます。