Slider

Data Input

최솟값~최댓값 사이에서 단일 값을 선택하는 범용 슬라이더.

Usage

볼륨·밝기·굵기처럼 하나의 수치를 연속 구간에서 고를 때 라벨·값 표시·눈금·범위(thumb 2개)처럼 자유 조립이 필요하면 compound API(Slider.Root ~ Slider.Thumb)

import

import

import { Slider } from '@mildang/design-system/unofficial/Slider';

Slider 하나만 가져오면 Slider.Root · Slider.Label · Slider.Control · Slider.Track · Slider.Range · Slider.Thumb · Slider.ValueText · Slider.MarkerGroup · Slider.Marker · Slider.HiddenInput 를 그 아래에서 쓸 수 있다.

예제를 복사해 쓸 때 필요한 준비

@mildang/styled-system 은 이 저장소에서 Panda 가 생성하는 산출물이다. 저장소 안에서는 turbo run ship 이후 쓸 수 있고, 패키지 소비자는 자기 Panda 산출물이나 다른 레이아웃 수단으로 바꿔야 한다.

Anatomy

tsx

import { Slider } from '@mildang/design-system/unofficial/Slider';

export default function Example() {
  return (
    <SliderRoot>
      <SliderLabel />
      <SliderControl>
        <SliderTrack>
          <SliderRange />
        </SliderTrack>
        <SliderThumb>
          <SliderHiddenInput />
        </SliderThumb>
        <SliderMarkerGroup>
          <SliderMarker />
        </SliderMarkerGroup>
      </SliderControl>
      <SliderValueText />
    </SliderRoot>
  );
}

부품

필수 여부

반복

위치

SliderRoot

필수

SliderLabel

선택

SliderRoot 안에 둔다.

SliderControl

필수

SliderRoot 안에 둔다.

SliderTrack

필수

SliderControl 안에 둔다.

SliderRange

필수

SliderTrack 안에 둔다.

SliderThumb

필수

여러 개 가능

SliderControl 안에 둔다 — 범위는 index 0·1로 두 개 둔다.

SliderHiddenInput

선택

SliderThumb 안에 둔다.

SliderMarkerGroup

선택

SliderControl 안에 둔다.

SliderMarker

선택

여러 개 가능

SliderMarkerGroup 안에 둔다.

SliderValueText

선택

SliderRoot 안에 자유롭게 둔다.

  • SliderControl 안에는 SliderTrack(+SliderRange)과 SliderThumb(+SliderHiddenInput)를 둔다.
  • 범위(두 값)를 표현하려면 SliderThumb를 index 0·1로 두 개 둔다.
  • 값 포맷을 직접 구성하려면 Slider.Context로 api.value를 읽어 SliderValueText children에 넣는다.

API Reference

Slider Props

Prop

Type

Default

aria-label

string

지정 안 함

color

"primary.fill.base" | "neutral.fill.base" | "neutral.fill.high" | "warning.fill.base" | "info.fill.base" | "positive.fill.base" | "critical.fill.base" | "brand.fill.low" | "brand.fill.base" | "chip.fill.bg.brand" | "chip.fill.bg.neutral" | "chip.fill.bg.green" | "chip.fill.bg.orange" | "chip.fill.bg.blue" | "chip.fill.bg.red" | "chip.fill.fg.default" | "chip.fill.fg.icon" | "chip.fill.fg.text" | "chip.fill.fg.orange" | "primary.fill.low" | "colorPalette.fill.base" | "colorPalette.fill.high" | "colorPalette.fill.low" | "colorPalette.fill.bg.brand" | "colorPalette.fill.bg.neutral" | "colorPalette.fill.bg.green" | "colorPalette.fill.bg.orange" | "colorPalette.fill.bg.blue" | "colorPalette.fill.bg.red" | "colorPalette.fill.fg.default" | "colorPalette.fill.fg.icon" | "colorPalette.fill.fg.text" | "colorPalette.fill.fg.orange"

지정 안 함

defaultValue

number

지정 안 함

disabled

boolean

지정 안 함

form

string

지정 안 함

formatValue

(value: number) => string

지정 안 함

label

string

지정 안 함

marks

SliderMark[]

지정 안 함

max

number

100

min

number

0

name

string

지정 안 함

onValueChange

(value: number) => void

지정 안 함

onValueChangeEnd

(value: number) => void

지정 안 함

orientation

"horizontal" | "vertical"

지정 안 함

origin

"end" | "start" | "center"

지정 안 함

readOnly

boolean

지정 안 함

step

number

지정 안 함

tooltip

boolean

지정 안 함

value

number

지정 안 함

SliderRoot

값 상태와 ark-ui 컨텍스트를 관리하는 최상위 컨테이너다.

공개 Props 없음

SliderLabel

슬라이더 이름을 보여주는 라벨이다.

공개 Props 없음

SliderControl

트랙과 thumb를 감싸는 상호작용 영역이다.

공개 Props 없음

SliderTrack

값 범위를 시각화하는 배경 막대다.

공개 Props 없음

SliderRange

현재 값(범위면 두 thumb 사이)만큼 채워지는 활성 구간이다.

공개 Props 없음

SliderThumb

드래그·키보드(↑↓←→)로 값을 바꾸는 손잡이다.

공개 Props 없음

SliderHiddenInput

폼 제출용 hidden input이다.

공개 Props 없음

SliderMarkerGroup

값 눈금(marker)을 감싸는 컨테이너다.

공개 Props 없음

SliderMarker

특정 값 위치의 눈금 하나다.

공개 Props 없음

SliderValueText

현재 값을 텍스트로 보여준다. children으로 포맷을 직접 구성할 수 있다.

공개 Props 없음

예제

기본 사용

value·onValueChange로 상태를 직접 들고 있는 제어 모드 기본형이다.

import { useState } from 'react';
import { Slider } from '@mildang/design-system/unofficial/Slider';

const SliderDefaultExample = () => {
    const [v, setV] = useState(45);
    return <Slider aria-label="값" value={v} onValueChange={setV} min={0} max={100} />;
  };

export default SliderDefaultExample;

색상

color로 활성 구간·thumb 색을 fill 토큰 경로로 바꾼다. 미지정 시 neutral이다.

import { HStack, VStack } from '@mildang/styled-system/jsx';
import { Text } from '@mildang/design-system/Text';
import { Slider } from '@mildang/design-system/unofficial/Slider';

// color prop은 임의 색 토큰 경로를 받는다 (미지정 시 neutral)
// color 는 fill 토큰(role)만 허용 — 원시 팔레트(red.light.700 등)는 role 로 대체
const SLIDER_COLORS = [
  'brand.fill.base',
  'critical.fill.base',
  'warning.fill.base',
  'positive.fill.base',
  'info.fill.base',
] as const;

const SliderColorsExample = () => (
    <VStack gap="16" width="360px" alignItems="stretch">
      <HStack gap="8">
        <Text textStyle="caption-lg-medium" flexShrink="0" minWidth="120px" whiteSpace="nowrap">
          default (neutral)
        </Text>
        <Slider aria-label="default" defaultValue={45} />
      </HStack>
      {SLIDER_COLORS.map((color) => (
        <HStack key={color} gap="8">
          <Text textStyle="caption-lg-medium" flexShrink="0" minWidth="120px" whiteSpace="nowrap">
            {color}
          </Text>
          <Slider aria-label={color} defaultValue={45} color={color} />
        </HStack>
      ))}
    </VStack>
  );

export default SliderColorsExample;

눈금(marks)

marks로 값 위치마다 점과 라벨을 표시한다.

import { useState } from 'react';
import { Slider } from '@mildang/design-system/unofficial/Slider';

const SliderMarksExample = () => {
    const [v, setV] = useState(30);
    return (
      <div style={{ width: 360 }}>
        <Slider
          aria-label="값"
          value={v}
          onValueChange={setV}
          min={0}
          max={100}
          marks={[
            { value: 0, label: '0' },
            { value: 25, label: '25' },
            { value: 50, label: '50' },
            { value: 75, label: '75' },
            { value: 100, label: '100' },
          ]}
        />
      </div>
    );
  };

export default SliderMarksExample;

Origin

origin이 range 채움 기준점을 정한다. center는 중앙부터 채워 밸런스·팬(-100~100)처럼 음수 구간에 적합하다.

import { useState } from 'react';
import { VStack } from '@mildang/styled-system/jsx';
import { Text } from '@mildang/design-system/Text';
import { Slider } from '@mildang/design-system/unofficial/Slider';

const SliderOriginExample = () => {
    const [v, setV] = useState(-30);
    return (
      <VStack gap="16" width="360px" alignItems="stretch">
        <VStack gap="4" alignItems="stretch">
          <Text textStyle="caption-lg-medium">origin="start" (기본)</Text>
          <Slider aria-label="start" defaultValue={40} min={0} max={100} />
        </VStack>
        <VStack gap="4" alignItems="stretch">
          <Text textStyle="caption-lg-medium">origin="center" ({v})</Text>
          <Slider aria-label="center" value={v} onValueChange={setV} min={-100} max={100} origin="center" />
        </VStack>
      </VStack>
    );
  };

export default SliderOriginExample;

값 툴팁

tooltip을 주면 드래그·포커스 중 thumb 위에 DS Tooltip으로 현재 값을 보여준다.

import { useState } from 'react';
import { HStack, VStack } from '@mildang/styled-system/jsx';
import { Slider } from '@mildang/design-system/unofficial/Slider';
import { Text } from '@mildang/design-system/Text';

const SliderWithTooltipExample = () => {
    const [v, setV] = useState(50);
    const [v2, setV2] = useState(50);
    return (
      <VStack gap="32" width="320px" alignItems="stretch" style={{ paddingTop: 32 }}>
        <Slider aria-label="볼륨" value={v} onValueChange={setV} tooltip formatValue={(n) => `${n}%`} />
        {/* 라벨 + 값 + 툴팁 */}
        <HStack gap="8">
          <Text textStyle="caption-lg-medium" flexShrink="0" whiteSpace="nowrap">
            볼륨
          </Text>
          <Slider aria-label="볼륨" value={v2} onValueChange={setV2} tooltip formatValue={(n) => `${n}%`} />
          <Text textStyle="caption-lg" minWidth="40px" flexShrink="0" textAlign="right">
            {v2}%
          </Text>
        </HStack>
      </VStack>
    );
  };

export default SliderWithTooltipExample;

비활성

비활성 상태는 상호작용과 값 변경이 모두 막힌다.

import { Slider } from '@mildang/design-system/unofficial/Slider';

const SliderDisabledExample = () => <Slider aria-label="값" defaultValue={40} disabled />;

export default SliderDisabledExample;

읽기 전용

값 변경(키보드·드래그)만 막고 포커스·hover는 허용한다.

import { Slider } from '@mildang/design-system/unofficial/Slider';

const SliderReadOnlyExample = () => <Slider aria-label="값" defaultValue={40} readOnly />;

export default SliderReadOnlyExample;

Step

step으로 증가 단위를 조정한다. 소수 step은 표시 자리수를 step에 맞춰 함께 보여준다.

import { useState } from 'react';
import { HStack, VStack } from '@mildang/styled-system/jsx';
import { Text } from '@mildang/design-system/Text';
import { Slider } from '@mildang/design-system/unofficial/Slider';

const SliderStepExample = () => {
    const [v, setV] = useState(0.5);
    const [v5, setV5] = useState(50);
    return (
      <VStack gap="16" width="320px" alignItems="stretch">
        <VStack gap="4" alignItems="stretch">
          <Text textStyle="caption-lg-medium">step 0.01</Text>
          <HStack gap="8">
            <Slider aria-label="0.01 단위" value={v} onValueChange={setV} min={0} max={1} step={0.01} />
            <Text textStyle="caption-lg" minWidth="32px" flexShrink="0" textAlign="right">
              {v.toFixed(2)}
            </Text>
          </HStack>
        </VStack>
        <VStack gap="4" alignItems="stretch">
          <Text textStyle="caption-lg-medium">step 5</Text>
          <HStack gap="8">
            <Slider aria-label="5 단위" value={v5} onValueChange={setV5} min={0} max={100} step={5} />
            <Text textStyle="caption-lg" minWidth="32px" flexShrink="0" textAlign="right">
              {v5}
            </Text>
          </HStack>
        </VStack>
      </VStack>
    );
  };

export default SliderStepExample;

세로 방향

orientation="vertical"이면 세로 트랙에 값 툴팁이 오른쪽으로 표시된다.

import { useState } from 'react';
import { Slider } from '@mildang/design-system/unofficial/Slider';

const SliderVerticalExample = () => {
    const [v, setV] = useState(50);
    return (
      <div style={{ height: 200 }}>
        <Slider
          aria-label="값"
          value={v}
          onValueChange={setV}
          orientation="vertical"
          tooltip
          formatValue={(n) => `${n}%`}
        />
      </div>
    );
  };

export default SliderVerticalExample;

세로 + 눈금

세로 슬라이더의 값 눈금(dot)은 track 가로 중앙, 라벨은 오른쪽에 표시되며 아래(min)에서 위(max) 순으로 읽힌다.

import { useState } from 'react';
import { Slider } from '@mildang/design-system/unofficial/Slider';

const SliderVerticalMarksExample = () => {
    const [v, setV] = useState(30);
    return (
      <div style={{ height: 240 }}>
        <Slider
          aria-label="값"
          value={v}
          onValueChange={setV}
          orientation="vertical"
          min={0}
          max={100}
          marks={[
            { value: 0, label: '0' },
            { value: 25, label: '25' },
            { value: 50, label: '50' },
            { value: 75, label: '75' },
            { value: 100, label: '100' },
          ]}
        />
      </div>
    );
  };

export default SliderVerticalMarksExample;

세로 채널 여러 개

값·슬라이더·라벨을 세로로 쌓은 채널을 나란히 배치하는 믹서 형태 패턴이다.

import { HStack, VStack } from '@mildang/styled-system/jsx';
import { useState } from 'react';
import { Text } from '@mildang/design-system/Text';
import { Slider } from '@mildang/design-system/unofficial/Slider';

// 세로 슬라이더 채널 (값 위 · 슬라이더 · 라벨 아래)
function VerticalChannel({ label, initial }: { label: string; initial: number }) {
  const [v, setV] = useState(initial);
  return (
    <VStack gap="8" alignItems="center" height="220px">
      <Text textStyle="caption-lg" flexShrink="0">
        {v}
      </Text>
      <Slider aria-label={label} value={v} onValueChange={setV} orientation="vertical" flex="1" />
      <Text textStyle="caption-lg-medium" flexShrink="0">
        {label}
      </Text>
    </VStack>
  );
}

const SliderVerticalWithLabelAndValueExample = () => (
    <HStack gap="24" alignItems="stretch">
      <VerticalChannel label="출력" initial={50} />
      <VerticalChannel label="입력" initial={30} />
    </HStack>
  );

export default SliderVerticalWithLabelAndValueExample;

앞뒤 라벨 + 값

슬라이더 앞뒤에 라벨과 현재 값을 함께 배치하는 실사용 패턴이다.

import { useState } from 'react';
import { HStack } from '@mildang/styled-system/jsx';
import { Text } from '@mildang/design-system/Text';
import { Slider } from '@mildang/design-system/unofficial/Slider';

const SliderWithLabelAndValueExample = () => {
    const [v, setV] = useState(45);
    return (
      <HStack gap="8" width="320px">
        <Text textStyle="body-xl-semibold" flexShrink="0">

        </Text>
        <Slider aria-label="폰트 크기" value={v} onValueChange={setV} min={10} max={100} />
        <Text textStyle="caption-lg" minWidth="24px" flexShrink="0" textAlign="right">
          {v}
        </Text>
      </HStack>
    );
  };

export default SliderWithLabelAndValueExample;

Compound — 라벨 + 값 표시

compound API로 Label(왼쪽)·ValueText(오른쪽) 헤더를 조립하고 아래 Control을 둔다.

코드

import { Slider } from '@mildang/design-system/unofficial/Slider';
import { HStack, VStack } from '@mildang/styled-system/jsx';

const SliderCompoundWithValueTextExample = () => (
    <Slider.Root defaultValue={[45]}>
      <VStack gap="4" alignItems="stretch" width="320px">
        <HStack justifyContent="space-between">
          <Slider.Label>볼륨</Slider.Label>
          <Slider.ValueText />
        </HStack>
        <Slider.Control>
          <Slider.Track>
            <Slider.Range />
          </Slider.Track>
          <Slider.Thumb index={0}>
            <Slider.HiddenInput />
          </Slider.Thumb>
        </Slider.Control>
      </VStack>
    </Slider.Root>
  );

export default SliderCompoundWithValueTextExample;

Compound — 범위(thumb 2개)

SliderThumb 2개로 범위를 표현한다. Range가 두 thumb 사이를 채우고, Slider.Context로 값 포맷(구분자·단위)을 자유롭게 구성한다. minStepsBetweenThumbs로 최소 간격을 줄 수 있다.

코드

import { HStack, VStack } from '@mildang/styled-system/jsx';
import { Slider } from '@mildang/design-system/unofficial/Slider';

// range 컨트롤(Track + Range + thumb 2개) 헬퍼
function RangeControl() {
  return (
    <Slider.Control>
      <Slider.Track>
        <Slider.Range />
      </Slider.Track>
      <Slider.Thumb index={0}>
        <Slider.HiddenInput />
      </Slider.Thumb>
      <Slider.Thumb index={1}>
        <Slider.HiddenInput />
      </Slider.Thumb>
    </Slider.Control>
  );
}

const SliderCompoundRangeExample = () => (
    <VStack gap="24" width="360px" alignItems="stretch">
      {/* 1) 기본 ValueText — 콤마(", ")로 join */}
      <Slider.Root defaultValue={[20, 80]} min={0} max={100}>
        <VStack gap="4" alignItems="stretch">
          <HStack justifyContent="space-between">
            <Slider.Label>범위 (기본 콤마)</Slider.Label>
            <Slider.ValueText />
          </HStack>
          <RangeControl />
        </VStack>
      </Slider.Root>

      {/* 2) "~" 구분자 (Slider.Context로 커스텀) */}
      <Slider.Root defaultValue={[20, 80]} min={0} max={100}>
        <VStack gap="4" alignItems="stretch">
          <HStack justifyContent="space-between">
            <Slider.Label>범위 (~)</Slider.Label>
            <Slider.Context>
              {(api) => <Slider.ValueText>{api.value.join(' ~ ')}</Slider.ValueText>}
            </Slider.Context>
          </HStack>
          <RangeControl />
        </VStack>
      </Slider.Root>

      {/* 3) 커스텀 포맷 */}
      <Slider.Root defaultValue={[250, 700]} min={0} max={1000} step={50}>
        <VStack gap="4" alignItems="stretch">
          <HStack justifyContent="space-between">
            <Slider.Label>커스텀 포맷</Slider.Label>
            <Slider.Context>
              {(api) => <Slider.ValueText>{`US$${api.value[0]} – US$${api.value[1]}`}</Slider.ValueText>}
            </Slider.Context>
          </HStack>
          <RangeControl />
        </VStack>
      </Slider.Root>

      {/* 4) 최소 간격 (minStepsBetweenThumbs=20) — 두 thumb가 20보다 가까워질 수 없음 */}
      <Slider.Root defaultValue={[30, 70]} min={0} max={100} minStepsBetweenThumbs={20}>
        <VStack gap="4" alignItems="stretch">
          <HStack justifyContent="space-between">
            <Slider.Label>최소 간격 20</Slider.Label>
            <Slider.Context>
              {(api) => <Slider.ValueText>{api.value.join(' ~ ')}</Slider.ValueText>}
            </Slider.Context>
          </HStack>
          <RangeControl />
        </VStack>
      </Slider.Root>
    </VStack>
  );

export default SliderCompoundRangeExample;