SEED Design

Slider

사용자가 특정 범위 내에서 값을 선택할 수 있는 입력 컴포넌트입니다.

import { Slider } from "seed-design/ui/slider";

export default function SliderPreview() {
  return <Slider min={0} max={100} defaultValues={[50]} getAriaLabel={() => "값"} />;
}

Installation

npx @seed-design/cli@latest add ui:slider

Props

Prop

Type

Examples

Basic

minmax를 설정하여 슬라이더의 값 범위를 지정합니다.

import { VStack } from "@seed-design/react";
import { Slider } from "seed-design/ui/slider";

export default function SliderBasic() {
  return (
    <VStack gap="spacingY.componentDefault" width="full">
      <Slider min={0} max={10} defaultValues={[5]} getAriaLabel={() => "값"} />
      <Slider min={0} max={1000} defaultValues={[600]} getAriaLabel={() => "값"} />
    </VStack>
  );
}

Steps

특정 간격으로만 값을 선택할 수 있도록 설정할 수 있습니다.

import { Slider } from "seed-design/ui/slider";
import { useState } from "react";
import { VStack, Text } from "@seed-design/react";

export default function SliderSteps() {
  const [value, setValue] = useState([50]);

  return (
    <VStack gap="spacingY.componentDefault" width="full" align="center">
      <Slider
        min={0}
        max={100}
        step={10}
        values={value}
        onValuesChange={setValue}
        getAriaLabel={() => "값"}
      />
      <Text>{JSON.stringify(value)}</Text>
    </VStack>
  );
}

Allowed Values

allowedValues prop을 사용하여 슬라이더에서 선택할 수 있는 값을 제한할 수 있습니다. allowedValues가 지정된 경우 stepminStepsBetweenThumbs은 무시됩니다.

import { VStack, Text } from "@seed-design/react";
import { Slider } from "seed-design/ui/slider";
import { useState } from "react";

const ALLOWED_VALUES = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29];

export default function SliderAllowedValues() {
  const [values, setValues] = useState([ALLOWED_VALUES[0], ALLOWED_VALUES[2]]);

  return (
    <VStack gap="spacingY.componentDefault" width="full" align="center">
      <Slider
        min={0}
        max={30}
        values={values}
        onValuesChange={setValues}
        allowedValues={ALLOWED_VALUES}
        markers={ALLOWED_VALUES.map((value) => ({ label: value, value }))}
        getAriaLabel={() => "값"}
      />
      <Text>{JSON.stringify(values)}</Text>
    </VStack>
  );
}

With Ticks

슬라이더 트랙에 눈금을 표시할 수 있습니다.

Thin

continuous하다고 느껴질 만큼 step이 충분히 작을 때 주로 사용합니다.

import { Slider } from "seed-design/ui/slider";

export default function SliderTicksThin() {
  return (
    <Slider
      min={0}
      max={100}
      step={0.1}
      defaultValues={[50]}
      ticks={[10, 20, 30, 40, 50, 60, 70, 80, 90]}
      tickWeight="thin"
      getAriaLabel={() => "값"}
    />
  );
}

Thick

discrete하다고 느껴질 만큼 step이 충분히 클 때 또는 tick에만 thumb이 위치할 수 있을 때 주로 사용합니다.

import { Slider } from "seed-design/ui/slider";

export default function SliderTicksThick() {
  return (
    <Slider
      min={0}
      max={100}
      step={10}
      defaultValues={[50]}
      ticks={[10, 20, 30, 40, 50, 60, 70, 80, 90]}
      tickWeight="thick"
      getAriaLabel={() => "값"}
    />
  );
}

With Markers

슬라이더 아래에 마커를 표시할 수 있습니다.

주의

스크린 리더는 marker를 읽지 않습니다. 특정 값을 선택했을 때 사용자에게 알려야 하는 경우 getAriaValuetext prop을 사용하여 현재 선택된 값을 설명하는 문구를 함께 제공하세요.

import { VStack } from "@seed-design/react";
import { Slider } from "seed-design/ui/slider";

export default function SliderMarkers() {
  return (
    <VStack gap="spacingY.componentDefault" width="full">
      <Slider
        min={0}
        max={100}
        defaultValues={[50]}
        markers={[
          { value: 0, label: "0°C" },
          { value: 25, label: "25°C" },
          { value: 50, label: "50°C" },
          { value: 75, label: "75°C" },
          { value: 100, label: "100°C" },
        ]}
        getAriaValuetext={(value) => `${value}°C`}
        getValueIndicatorLabel={({ value }) => `${value}°C`}
        getAriaLabel={() => "온도"}
      />
      <Slider
        min={0}
        max={100}
        defaultValues={[30]}
        markers={[0, 20, 40, 60, 80, 100]}
        getAriaLabel={() => "값"}
      />
    </VStack>
  );
}

Controlled

valuesonValuesChange props를 사용하여 슬라이더의 상태를 외부에서 제어합니다.

import { VStack, HStack, Text } from "@seed-design/react";
import { Slider } from "seed-design/ui/slider";
import { ActionButton } from "seed-design/ui/action-button";
import { useState } from "react";

const DEFAULT_VALUE = [50];

export default function SliderControlled() {
  const [value, setValue] = useState(DEFAULT_VALUE);

  return (
    <VStack gap="spacingY.componentDefault" width="full" align="center">
      <Slider min={0} max={100} values={value} onValuesChange={setValue} getAriaLabel={() => "값"} />
      <Text>{JSON.stringify(value)}</Text>
      <HStack gap="spacingY.componentDefault">
        <ActionButton type="button" onClick={() => setValue([0])} variant="neutralWeak">
          Set Min
        </ActionButton>
        <ActionButton type="button" onClick={() => setValue(DEFAULT_VALUE)} variant="neutralWeak">
          Reset
        </ActionButton>
        <ActionButton type="button" onClick={() => setValue([100])} variant="neutralWeak">
          Set Max
        </ActionButton>
      </HStack>
    </VStack>
  );
}

Listening to Value Changes

  • onValuesChange
    • 슬라이더의 값이 변경될 때마다 호출됩니다. 이 prop을 사용하여 슬라이더의 값을 실시간으로 추적할 수 있습니다.
  • onValuesCommit
    • 슬라이더의 값 변경이 완료되었을 때 호출됩니다. 사용자가 슬라이더 조작을 마치고 손을 뗄 때 값을 확정하는 데 유용합니다.
import { VStack, Text, HStack } from "@seed-design/react";
import { Slider } from "seed-design/ui/slider";
import { useState } from "react";

export default function SliderOnValuesCommit() {
  const [value, setValue] = useState([20]);
  const [committedValue, setCommittedValue] = useState([20]);

  return (
    <VStack gap="x4" width="full" align="center">
      <Slider
        min={0}
        max={100}
        values={value}
        onValuesChange={setValue}
        onValuesCommit={setCommittedValue}
        getAriaLabel={() => "값"}
      />
      <HStack gap="x4">
        <Text>Current value: {JSON.stringify(value)}</Text>
        <Text>Committed value: {JSON.stringify(committedValue)}</Text>
      </HStack>
    </VStack>
  );
}

Disabled

import { VStack } from "@seed-design/react";
import { Slider } from "seed-design/ui/slider";

export default function SliderDisabled() {
  return (
    <VStack width="full" gap="spacingY.componentDefault">
      <Slider min={0} max={100} defaultValues={[50]} disabled getAriaLabel={() => "값"} />
      <Slider
        min={0}
        max={100}
        defaultValues={[25, 75]}
        disabled
        getAriaLabel={(thumbIndex) => (thumbIndex === 0 ? "최소값" : "최대값")}
      />
    </VStack>
  );
}

Hide Range

hideRange prop을 사용하여 기본 range 색상을 숨기고 커스텀 스타일을 적용할 수 있습니다.

import { VStack } from "@seed-design/react";
import { Slider } from "seed-design/ui/slider";

export default function SliderHideRange() {
  return (
    <VStack gap="spacingY.componentDefault" width="full">
      <Slider min={0} max={100} defaultValues={[60]} hideRange getAriaLabel={() => "값"} />
      <Slider
        min={0}
        max={100}
        defaultValues={[20, 80]}
        hideRange
        getAriaLabel={(thumbIndex) => (thumbIndex === 0 ? "최소값" : "최대값")}
      />
    </VStack>
  );
}

Customizing Value Indicator

Value Indicator Label

getValueIndicatorLabel prop을 사용하여 thumb 위에 표시되는 툴팁의 내용을 커스텀할 수 있습니다.

주의

Value Indicator는 thumb을 누르고 있을 때만 표시되며 스크린 리더는 Value Indicator의 내용을 읽지 않습니다. Value Indicator에 의미가 있는 내용을 포함하는 경우, getAriaValuetext prop을 함께 사용하는 것을 권장합니다.

import { Slider } from "seed-design/ui/slider";

const formatter = new Intl.NumberFormat("ko-KR", { style: "decimal" });

export default function SliderCustomValueIndicatorLabel() {
  return (
    <Slider
      min={0}
      max={1_000_000}
      defaultValues={[20_000, 500_000]}
      getValueIndicatorLabel={({ value, thumbIndex }) => (
        <>
          thumb {thumbIndex}
          <br />
          {formatter.format(value)}
        </>
      )}
      getAriaValuetext={formatter.format}
      getAriaLabel={() => "값"}
    />
  );
}

Hide Value Indicator

hideValueIndicator prop을 사용하여 thumb 위에 표시되는 Value Indicator 툴팁을 숨길 수 있습니다.

import { Slider } from "seed-design/ui/slider";

export default function SliderHideValueIndicator() {
  return (
    <Slider min={0} max={100} defaultValues={[50]} hideValueIndicator getAriaLabel={() => "값"} />
  );
}

Range Slider

두 개의 thumb를 사용하여 범위를 선택할 수 있습니다.

import { VStack } from "@seed-design/react";
import { Slider } from "seed-design/ui/slider";
import { useState } from "react";

export default function SliderRange() {
  const [priceRange, setPriceRange] = useState([20, 80]);

  return (
    <VStack gap="spacingY.componentDefault" width="full">
      <Slider
        min={0}
        max={100}
        values={priceRange}
        onValuesChange={setPriceRange}
        getAriaLabel={(thumbIndex) => (thumbIndex === 0 ? "최소값" : "최대값")}
      />
    </VStack>
  );
}

Minimum Steps Between Thumbs

두 thumb 사이의 최소 간격을 설정할 수 있습니다.

import { VStack, Text } from "@seed-design/react";
import { Slider } from "seed-design/ui/slider";
import { useState } from "react";

export default function SliderRangeMinSteps() {
  const [values, setValues] = useState([20, 80]);

  return (
    <VStack gap="spacingY.componentDefault" width="full" align="center">
      <Slider
        min={0}
        max={100}
        values={values}
        onValuesChange={setValues}
        step={5}
        minStepsBetweenThumbs={6} // step이 5이므로, value의 최소 간격은 30이 됩니다.
        getAriaLabel={(thumbIndex) => (thumbIndex === 0 ? "최소값" : "최대값")}
      />
      <Text>{JSON.stringify(values)}</Text>
    </VStack>
  );
}

Accessibility

getAriaValuetext

스크린 리더는 기본적으로 각 thumb이 가리키는 숫자 값(value)을 읽습니다. 숫자 값만으로 정보를 충분히 전달할 수 없는 경우 getAriaValuetext prop을 사용하여 인간 친화적인 설명을 제공하세요. 단위를 추가하거나, 값의 의미를 설명하는 문구를 포함할 수 있습니다.

import { VStack, Text } from "@seed-design/react";
import { useState } from "react";
import { Slider } from "seed-design/ui/slider";

const days = ["일", "월", "화", "수", "목", "금", "토"];

function getHumanReadableDayOfWeek(value: number) {
  if (days[value] === undefined) throw new Error("Invalid day value");

  return `${days[value]}요일`;
}

export default function SliderGetAriaValuetext() {
  const [values, setValues] = useState([1, 3]);

  return (
    <VStack gap="spacingY.componentDefault" width="full" align="center">
      <Slider
        min={0}
        max={days.length - 1}
        minStepsBetweenThumbs={1}
        markers={days.map((label, value) => ({ label, value }))}
        ticks={days.slice(1, -1).map((_, index) => index + 1)}
        tickWeight="thick"
        values={values}
        onValuesChange={setValues}
        getAriaLabel={(thumbIndex) => (thumbIndex === 0 ? "시작" : "종료")}
        getAriaValuetext={getHumanReadableDayOfWeek}
        getValueIndicatorLabel={({ value }) => getHumanReadableDayOfWeek(value)}
      />
      <Text>values: {JSON.stringify(values)}</Text>
      <Text>aria-valuetext: {JSON.stringify(values.map(getHumanReadableDayOfWeek))}</Text>
    </VStack>
  );
}

getAriaLabel and getAriaLabelledby

getAriaLabel 또는 getAriaLabelledby prop을 사용하여 각 thumb에 대한 설명을 제공하세요. 스크린 리더 사용자가 각 thumb의 용도를 이해하는 데 도움을 줍니다.

특히, 범위를 선택하는 슬라이더의 경우 각 thumb이 범위에서 어떤 역할을 하는지 설명해야 합니다. (최소-최대, 시작-종료 등)

import { VStack, Text, type SliderRootProps } from "@seed-design/react";
import { useState } from "react";
import { Slider } from "seed-design/ui/slider";

const getAriaLabel: NonNullable<SliderRootProps["getAriaLabel"]> = (thumbIndex) =>
  thumbIndex === 0 ? "최소값" : "최대값";

export default function SliderGetAriaLabel() {
  const [values, setValues] = useState([10, 30]);

  return (
    <VStack gap="spacingY.componentDefault" width="full" align="center">
      <Slider
        min={0}
        max={100}
        values={values}
        onValuesChange={setValues}
        getAriaLabel={getAriaLabel}
      />
      <Text>values: {JSON.stringify(values)}</Text>
      <Text>aria-label: {JSON.stringify(values.map((_, index) => getAriaLabel(index)))}</Text>
    </VStack>
  );
}

Field Integration

label, description, errorMessage 등의 Field 관련 prop을 사용할 수 있습니다.

import { Divider, VStack } from "@seed-design/react";
import { Slider } from "seed-design/ui/slider";

const markers = [
  { value: 0, label: "매우 동의하지 않음" },
  { value: 14, label: "매우 동의함" },
];

export default function SliderField() {
  return (
    <VStack gap="x8" width="full">
      <Slider
        label="내일 날씨가 좋을 것 같다고 생각한다."
        min={0}
        max={14}
        defaultValues={[7]}
        ticks={[2, 4, 6, 8, 10, 12]}
        markers={markers}
        hideRange
        description="내일 날씨에 대한 당신의 기대감을 0~14 사이 숫자로 나타내 주세요."
        getAriaValuetext={(value) =>
          `${value} ${markers.find((marker) => marker.value === value)?.label ?? ""}`.trim()
        }
      />
      <Divider />
      <Slider
        label="Invalid Slider"
        labelWeight="bold"
        min={0}
        max={1000}
        defaultValues={[500]}
        invalid
        errorMessage="올바르지 않은 값입니다."
      />
    </VStack>
  );
}

RTL Support

import { VStack } from "@seed-design/react";
import { Slider } from "seed-design/ui/slider";

export default function SliderRtl() {
  return (
    <VStack gap="spacingY.componentDefault" width="full">
      <Slider
        dir="rtl"
        min={0}
        max={100}
        step={0.5}
        defaultValues={[36.5]}
        ticks={[36.5]}
        tickWeight="thick"
        markers={[
          { value: 0, label: "0°C" },
          { value: 36.5, label: "36.5°C" },
          { value: 100, label: "100°C" },
        ]}
        getAriaValuetext={(value) => `${value}°C`}
        getValueIndicatorLabel={({ value }) => `${value}°C`}
        getAriaLabel={() => "온도"}
      />
      <Slider
        dir="rtl"
        min={0}
        max={100}
        defaultValues={[30, 50]}
        ticks={[20, 40, 60, 80]}
        markers={[0, 20, 40, 60, 80, 100]}
        getAriaLabel={() => "값"}
      />
    </VStack>
  );
}

Last updated on