# Slider
URL: /lynx/components/slider
Source: https://github.com/daangn/seed-design/blob/dev/docs/content/lynx/components/slider.mdx
지정된 범위에서 하나 또는 두 개의 값을 선택하는 슬라이더 컴포넌트입니다.
Lynx Engine 최소 버전: 3.6
사용 가능 버전: @seed-design/lynx-react@0.8.0, @seed-design/lynx-css@0.12.0
## Preview
```tsx
import "./styles";
import { useSeedClassName } from "@seed-design/lynx-react";
import { Slider } from "@/components/ui/slider";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
"값"} />
);
}
```
## Installation
- npm: npx @seed-design/cli@latest add ui:slider --framework lynx
- pnpm: pnpm dlx @seed-design/cli@latest add ui:slider --framework lynx
- yarn: yarn dlx @seed-design/cli@latest add ui:slider --framework lynx
- bun: bun x @seed-design/cli@latest add ui:slider --framework lynx
## Props
### `Slider`
Registry의 `Slider`는 Slider primitive를 Field 안내 요소와 함께 조합한 편의 래퍼입니다. 한 개의 thumb을 사용할 때는 `defaultValues` 또는 `values`에 한 값을 넣고, 범위를 선택할 때는 두 값을 넣습니다.
Registry 래퍼는 다음 두 종류의 prop을 제공합니다.
- **Slider 상태·동작**: `low-level Root`의 `values`, `defaultValues`, `min`, `max`, `step`, `allowedValues`, `minStepsBetweenThumbs`, `dir`, `disabled`, `readOnly`, `invalid`, `onValuesChange`, `onValuesCommit`, `getAccessibilityLabel`, `getAccessibilityValueText`, `getValueIndicatorLabel`, `valueIndicatorTrigger`를 그대로 전달합니다.
- **화면과 Field 안내**: `label`, `labelWeight`, `indicator`, `description`, `errorMessage`, `showRequiredIndicator`, `markers`, `ticks`, `tickWeight`, `hideRange`, `hideValueIndicator`, `fieldRef`를 제공합니다. `markers`의 각 항목은 숫자이거나 `{ value, label?, align? }` 객체입니다.
`allowedValues`를 지정하면 허용된 값만 선택할 수 있으며 `step`과 `minStepsBetweenThumbs`보다 우선합니다. `values`와 `onValuesChange`를 함께 사용하면 controlled 상태가 되고, `onValuesCommit`은 값이 확정되는 시점에 호출됩니다.
## Usage
Registry의 조합이 아닌 `@seed-design/lynx-react` primitive를 직접 사용할 수도 있습니다. `SliderRoot` 안에서 `SliderControl`과 `SliderTrack`을 조합하고, track 안에 `SliderRange`와 필요한 `SliderTick`을 배치합니다. 각 thumb은 `SliderThumb`로 만들며 Value Indicator는 `SliderValueIndicatorRoot`, `SliderValueIndicatorArrow`와 그 안의 `SliderValueIndicatorArrowTip`, `SliderValueIndicatorLabel`로 구성합니다. 마커가 필요하면 `SliderMarkers` 안에 `SliderMarker`를 둡니다.
```tsx
import { useState } from "@lynx-js/react";
import {
ActionButton,
SliderControl,
SliderMarker,
SliderMarkers,
SliderRange,
SliderRoot,
SliderThumb,
SliderTick,
SliderTrack,
SliderValueIndicatorArrow,
SliderValueIndicatorArrowTip,
SliderValueIndicatorLabel,
SliderValueIndicatorRoot,
} from "@seed-design/lynx-react";
export function PriceSlider() {
const [values, setValues] = useState([40]);
const setPreset = () => {
setValues([60]);
};
return (
"가격"}
getAccessibilityValueText={(value) => `${value}원`}
>
0원
100원
60원으로 설정
);
}
```
`SliderThumb`의 `index`는 `values` 배열에서 thumb의 위치를 가리킵니다. package primitive를 직접 조합할 때도 값 변경과 제출·검증은 앱 상태와 요청 흐름에서 처리합니다.
## Examples
### Basic
`min`과 `max`로 선택 범위를 정하고 하나의 thumb으로 값을 선택합니다.
```tsx
import "./styles";
import { useSeedClassName } from "@seed-design/lynx-react";
import { Slider } from "@/components/ui/slider";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
"값"} />
"값"} />
);
}
```
### Steps
`step`으로 선택 간격을 설정해 일정한 단위의 값만 선택합니다.
```tsx
import "./styles";
import { useState } from "@lynx-js/react";
import { useSeedClassName } from "@seed-design/lynx-react";
import { Slider } from "@/components/ui/slider";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
const [value, setValue] = useState([50]);
function handleValuesChange(nextValues: number[]) {
"background only";
setValue(nextValues);
}
return (
"값"}
/>
{JSON.stringify(value)}
);
}
```
### Allowed Values
`allowedValues`로 선택 가능한 값을 명시합니다. 이 prop을 지정하면 `step`과 `minStepsBetweenThumbs`보다 `allowedValues`가 우선합니다.
```tsx
import "./styles";
import { useState } from "@lynx-js/react";
import { useSeedClassName } from "@seed-design/lynx-react";
import { Slider } from "@/components/ui/slider";
const ALLOWED_VALUES = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29];
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
const [values, setValues] = useState([ALLOWED_VALUES[0], ALLOWED_VALUES[2]]);
function handleValuesChange(nextValues: number[]) {
"background only";
setValues(nextValues);
}
return (
({ label: value, value }))}
getAccessibilityLabel={() => "값"}
/>
{JSON.stringify(values)}
);
}
```
### With Ticks
track 위에 선택 단위를 나타내는 눈금을 표시할 수 있습니다.
#### Thin
연속적인 조작처럼 보이도록 작은 간격을 사용하고 얇은 눈금을 표시합니다.
```tsx
import "./styles";
import { Slider } from "@/components/ui/slider";
import { useSeedClassName } from "@seed-design/lynx-react";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
"값"}
/>
);
}
```
#### Thick
값의 단계가 크거나 눈금 위치에서만 선택할 수 있는 경우 굵은 눈금을 표시합니다.
```tsx
import "./styles";
import { Slider } from "@/components/ui/slider";
import { useSeedClassName } from "@seed-design/lynx-react";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
"값"}
/>
);
}
```
### With Markers
slider 아래에 주요 값을 설명하는 marker를 표시합니다. marker의 시각적 텍스트는 native accessibility 값에 자동으로 포함되지 않으므로, 필요한 의미는 `getAccessibilityValueText`로도 전달하세요.
`markers`에는 숫자 또는 `{ value, label?, align? }` 객체를 전달할 수 있습니다. 객체에서 `align`을 생략하면 최소값은 시작, 최대값은 끝, 나머지는 가운데에 배치됩니다.
```tsx
import "./styles";
import { Slider } from "@/components/ui/slider";
import { useSeedClassName } from "@seed-design/lynx-react";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
`${value}°C`}
getValueIndicatorLabel={({ value }) => `${value}°C`}
getAccessibilityLabel={() => "온도"}
/>
"값"}
/>
);
}
```
### Controlled
`values`와 `onValuesChange`를 사용하여 슬라이더의 값을 앱 상태로 제어합니다.
```tsx
import "./styles";
import { useState } from "@lynx-js/react";
import { useSeedClassName } from "@seed-design/lynx-react";
import { Slider } from "@/components/ui/slider";
const DEFAULT_VALUE = [50];
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
const [value, setValue] = useState(DEFAULT_VALUE);
function handleValuesChange(nextValues: number[]) {
"background only";
setValue(nextValues);
}
function handleSetMin() {
"background only";
setValue([0]);
}
function handleReset() {
"background only";
setValue(DEFAULT_VALUE);
}
function handleSetMax() {
"background only";
setValue([100]);
}
return (
"값"}
/>
{JSON.stringify(value)}
Set Min
Reset
Set Max
);
}
```
### Listening to Value Changes
- `onValuesChange`: 드래그 중 값이 바뀔 때마다 호출됩니다. 화면에 현재 값을 표시하거나 앱 상태를 실시간으로 갱신할 때 사용합니다.
- `onValuesCommit`: 값 변경이 끝났을 때 호출됩니다. Lynx native에서는 값이 변경된 drag의 touch release에서 한 번 호출됩니다. 이 callback에 debounce를 추가할 필요는 없습니다.
```tsx
import "./styles";
import { useState } from "@lynx-js/react";
import { useSeedClassName } from "@seed-design/lynx-react";
import { Slider } from "@/components/ui/slider";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
const [value, setValue] = useState([20]);
const [committedValue, setCommittedValue] = useState([20]);
function handleValuesChange(nextValues: number[]) {
"background only";
setValue(nextValues);
}
function handleValuesCommit(nextValues: number[]) {
"background only";
setCommittedValue(nextValues);
}
return (
"값"}
/>
Current value: {JSON.stringify(value)}
Committed value: {JSON.stringify(committedValue)}
);
}
```
### Disabled
`disabled`를 사용하면 thumb을 조작할 수 없고 비활성 상태 스타일을 표시합니다.
```tsx
import "./styles";
import { useSeedClassName } from "@seed-design/lynx-react";
import { Slider } from "@/components/ui/slider";
export default function SliderDisabled() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
"값"}
/>
(thumbIndex === 0 ? "최소값" : "최대값")}
/>
);
}
```
### Hide Range
`hideRange`로 활성 구간 색상을 숨기고 track만 표시합니다.
```tsx
import "./styles";
import { useSeedClassName } from "@seed-design/lynx-react";
import { Slider } from "@/components/ui/slider";
export default function SliderHideRange() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
"값"}
/>
(thumbIndex === 0 ? "최소값" : "최대값")}
/>
);
}
```
### Customizing Value Indicator
#### Value Indicator Label
`getValueIndicatorLabel`로 thumb을 조작할 때 표시되는 Value Indicator의 텍스트를 바꿀 수 있습니다.
Value Indicator는 native touch 조작 중에 표시되는 보조 UI이며, 접근성 탐색이 읽는 값은 아닙니다. 의미 있는 단위나 설명을 제공해야 한다면 `getAccessibilityValueText`도 함께 사용하세요.
```tsx
import "./styles";
import { useSeedClassName } from "@seed-design/lynx-react";
import { Slider } from "@/components/ui/slider";
const formatter = new Intl.NumberFormat("ko-KR", { style: "decimal" });
export default function SliderCustomValueIndicatorLabel() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
(
{`thumb ${thumbIndex}\n${formatter.format(value)}`}
)}
getAccessibilityValueText={formatter.format}
getAccessibilityLabel={() => "값"}
/>
);
}
```
#### Value Indicator Trigger
`valueIndicatorTrigger`로 Value Indicator가 표시되는 조건을 정합니다. Lynx에서는 `"auto"`와 `"active"`를 사용할 수 있습니다.
- `"active"`: 현재 touch로 조작하는 thumb에 표시합니다.
- `"auto"` (기본값): native touch 환경에서는 `"active"`와 동일하게 동작합니다.
웹의 hover 기반 표시 조건이나 키보드 포커스 기반 동작은 Lynx native에 대응하지 않습니다.
```tsx
import "./styles";
import { useSeedClassName } from "@seed-design/lynx-react";
import { Slider } from "@/components/ui/slider";
export default function SliderValueIndicatorTrigger() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
"값"}
/>
"값"}
/>
auto와 active 모두 터치로 활성화된 동안에만 값 표시
);
}
```
#### Hide Value Indicator
`hideValueIndicator`로 thumb 위의 Value Indicator를 숨깁니다.
```tsx
import "./styles";
import { useSeedClassName } from "@seed-design/lynx-react";
import { Slider } from "@/components/ui/slider";
export default function SliderHideValueIndicator() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
"값"}
/>
);
}
```
### Range Slider
`defaultValues` 또는 `values`에 두 값을 전달하여 두 thumb 사이의 범위를 선택합니다.
```tsx
import "./styles";
import { Slider } from "@/components/ui/slider";
import { useState } from "@lynx-js/react";
import { useSeedClassName } from "@seed-design/lynx-react";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
const [priceRange, setPriceRange] = useState([20, 80]);
return (
(thumbIndex === 0 ? "최소값" : "최대값")}
/>
);
}
```
#### Minimum Steps Between Thumbs
`minStepsBetweenThumbs`로 두 thumb 사이에 유지할 최소 간격을 지정합니다.
```tsx
import "./styles";
import { Slider } from "@/components/ui/slider";
import { useState } from "@lynx-js/react";
import { useSeedClassName } from "@seed-design/lynx-react";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
const [values, setValues] = useState([20, 80]);
return (
(thumbIndex === 0 ? "최소값" : "최대값")}
/>
{JSON.stringify(values)}
);
}
```
### Accessibility
#### `getAccessibilityValueText`
native accessibility 탐색은 기본적으로 각 thumb에 `minimum , maximum , current ` 형식의 값을 제공합니다. 숫자만으로 의미가 충분하지 않다면 `getAccessibilityValueText`로 단위나 값의 의미를 포함한 설명을 제공하세요.
```tsx
import "./styles";
import { useState } from "@lynx-js/react";
import { useSeedClassName } from "@seed-design/lynx-react";
import { Slider } from "@/components/ui/slider";
const days = ["일", "월", "화", "수", "목", "금", "토"];
function getHumanReadableDayOfWeek(value: number) {
if (days[value] === undefined) throw new Error("Invalid day value");
return `${days[value]}요일`;
}
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
const [values, setValues] = useState([1, 3]);
return (
({ label, value }))}
ticks={days.slice(1, -1).map((_, index) => index + 1)}
tickWeight="thick"
values={values}
onValuesChange={setValues}
getAccessibilityLabel={(thumbIndex) => (thumbIndex === 0 ? "시작" : "종료")}
getAccessibilityValueText={getHumanReadableDayOfWeek}
getValueIndicatorLabel={({ value }) => getHumanReadableDayOfWeek(value)}
/>
values: {JSON.stringify(values)}
accessibility-value-text: {JSON.stringify(values.map(getHumanReadableDayOfWeek))}
);
}
```
#### `getAccessibilityLabel`
`getAccessibilityLabel`로 각 thumb의 용도를 설명합니다. 특히 range slider에서는 각 thumb이 최소값·최대값 중 어떤 역할인지 구분할 수 있는 label을 제공하세요.
```tsx
import "./styles";
import { useState } from "@lynx-js/react";
import { useSeedClassName } from "@seed-design/lynx-react";
import { Slider } from "@/components/ui/slider";
const getAccessibilityLabel = (thumbIndex: number) => (thumbIndex === 0 ? "최소값" : "최대값");
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
const [values, setValues] = useState([10, 30]);
return (
values: {JSON.stringify(values)}
accessibility-label:{" "}
{JSON.stringify(values.map((_, index) => getAccessibilityLabel(index)))}
);
}
```
### Field Integration
`label`, `labelWeight`, `indicator`, `description`, `errorMessage`, `showRequiredIndicator`를 사용해 슬라이더 주변에 Field 안내를 표시할 수 있습니다. `invalid`가 `true`이고 `errorMessage`가 있으면 오류 안내를 표시합니다.
```tsx
import "./styles";
import { useSeedClassName } from "@seed-design/lynx-react";
import { Slider } from "@/components/ui/slider";
const markers = [
{ value: 0, label: "매우 동의하지 않음" },
{ value: 14, label: "매우 동의함" },
];
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
`${value} ${markers.find((marker) => marker.value === value)?.label ?? ""}`.trim()
}
/>
);
}
```
### RTL Support
`dir="rtl"`로 값의 시작과 끝이 오른쪽에서 왼쪽으로 배치되는 슬라이더를 만들 수 있습니다.
```tsx
import "./styles";
import { useSeedClassName } from "@seed-design/lynx-react";
import { Slider } from "@/components/ui/slider";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
`${value}°C`}
getValueIndicatorLabel={({ value }) => `${value}°C`}
getAccessibilityLabel={() => "온도"}
/>
"값"}
/>
);
}
```
## 웹 버전과의 차이
Lynx `Slider`는 React Slider와 다음과 같은 차이가 있습니다.
- **렌더링 요소**: HTML form input과 DOM 요소 대신 native ``와 ``를 사용합니다.
- **입력 이벤트**: 브라우저 pointer/keyboard 이벤트 대신 native touch 입력을 사용하며, 값 변경은 `onValuesChange`, 확정은 `onValuesCommit`으로 전달합니다.
- **Value Indicator**: `valueIndicatorTrigger="auto"`도 native에서는 touch-active와 같고 hover로 표시되지 않습니다. `"active"`와 동일한 결과를 제공합니다.
- **접근성**: DOM ARIA 대신 `getAccessibilityLabel`과 `getAccessibilityValueText`로 native accessibility 이름과 값을 제공합니다.
- **Field 안내**: Registry의 `label`, `description`, `errorMessage`는 native 레이아웃으로 표시됩니다. DOM id를 통한 label·description 연결은 사용하지 않습니다.
- **범위 선택**: `values` 배열과 두 개의 native thumb으로 범위를 표현합니다.
## Lynx 미지원 기능
Lynx에는 HTML form과 브라우저 키보드 모델이 없으므로 다음 React 전용 기능은 실행 예제로 제공하지 않습니다.
| 기능 | Lynx에서의 대체 또는 제한 |
| ------------------------------ | --------------------------------------------------------------------------------------------------- |
| `form` 제출, `React Hook Form` | `values`와 `onValuesChange`로 앱 상태를 관리하고, 제출은 앱의 요청 흐름에서 처리합니다. |
| `HiddenInput` | HTML hidden input이 없습니다. 제출할 값은 앱 상태에서 직접 구성합니다. |
| `name` | native form field name이 없습니다. 앱 상태의 키나 요청 payload를 사용합니다. |
| 브라우저 검증 및 form field 연결 | `invalid`, `errorMessage`로 화면 오류를 표시하고 검증은 앱에서 수행합니다. |
| `getAriaLabelledby`와 DOM ARIA | DOM id 연결이 없습니다. `getAccessibilityLabel`과 `getAccessibilityValueText`로 native accessibility를 제공합니다. |
| keyboard, focus, focus-visible | 키보드 thumb focus 모델이 없습니다. native touch와 accessibility 탐색을 사용합니다. |
| hover | native에는 hover 상호작용이 없습니다. `valueIndicatorTrigger="auto"`는 `"active"`와 같이 touch-active로 동작합니다. |
따라서 Form (Uncontrolled)과 React Hook Form은 Lynx 문서의 실행 예제에서 제외했습니다. 제출·검증이 필요한 경우에도 슬라이더 값을 앱 상태에 연결하고, 앱의 제출·검증 로직과 native accessibility 안내를 사용하세요.