Vinyl UI logoVinyl UI

Select

목록에서 값을 선택하는 드롭다운 컴포넌트

Select는 여러 파트를 조합해 사용합니다. Select(루트) 안에 트리거 영역(SelectControl > SelectTrigger > SelectValue)과 옵션 목록(SelectContent > SelectItem > SelectItemText)을 구성합니다. 트리거 우측의 chevron-down 아이콘은 SelectTrigger가 기본으로 렌더합니다.

Select제어(controlled) 컴포넌트입니다. value(항상 string[])와 onChangeValue가 필수이며, 단일 선택이어도 value는 배열로 관리합니다(선택값은 value[0]). 단일 선택 편의는 커스텀 훅으로 감싸는 것을 권장합니다.

기본 예시

import { useState } from 'react';

import {
  Select,
  SelectControl,
  SelectTrigger,
  SelectValue,
  SelectContent,
  SelectItem,
  SelectItemText,
} from '@bigmobility/vinyl-ui';

const REGIONS = [
  { label: '서울', value: 'seoul' },
  { label: '부산', value: 'busan' },
  { label: '대구', value: 'daegu' },
  { label: '인천 (준비 중)', value: 'incheon', disabled: true },
];

export default function RegionSelect() {
  const [value, setValue] = useState<string[]>([]);

  const handleChange = ({ value }: {
    name: string;
    value: string[];
  }) => {
    setValue(value);
  };

  return (
    <Select
      items={REGIONS}
      name="region"
      value={value}
      onChangeValue={handleChange}
    >
      <SelectControl>
        <SelectTrigger>
          <SelectValue placeholder="지역 선택" />
        </SelectTrigger>
      </SelectControl>
      <SelectContent>
        {REGIONS.map((item) => (
          <SelectItem
            key={item.value}
            item={item}
          >
            <SelectItemText>{item.label}</SelectItemText>
          </SelectItem>
        ))}
      </SelectContent>
    </Select>
  );
}

Placeholder

값이 선택되지 않았을 때 SelectValueplaceholder가 표시되며, 색이 text.input-soft로 흐려집니다.

<SelectTrigger>
  <SelectValue placeholder="지역을 선택하세요" />
</SelectTrigger>

다중 선택

multiple을 주면 여러 값을 동시에 선택할 수 있습니다. value는 단일 선택과 동일하게 string[]이며, 선택된 값들이 배열에 누적됩니다.

const [value, setValue] = useState<string[]>([]);

<Select
  items={REGIONS}
  name="region"
  value={value}
  multiple
  onChangeValue={handleChange}
>
  {/* ... */}
</Select>

Error

hasError를 주면 트리거가 에러 스타일(배경 layout.bg-issue, 테두리 layout.issue-line)로 표시됩니다.

<Select
  items={REGIONS}
  name="region"
  value={value}
  hasError
  onChangeValue={handleChange}
>
  {/* ... */}
</Select>

Disabled

disabled를 주면 트리거가 비활성화되고 열 수 없습니다. 개별 옵션은 items의 각 항목에 disabled: true로 비활성화합니다.

<Select
  items={REGIONS}
  name="region"
  value={value}
  disabled
  onChangeValue={handleChange}
>
  {/* ... */}
</Select>

옵션 그룹

옵션을 묶을 때는 SelectGroup + SelectGroupLabel로 그룹 제목을 붙입니다. 루트 items에는 전체 옵션을 평탄하게 넘기고, SelectContent 안에서 group 기준으로 묶어 렌더합니다.

import { SelectGroup, SelectGroupLabel } from '@bigmobility/vinyl-ui';

const REGIONS = [
  { label: '서울', value: 'seoul', group: '수도권' },
  { label: '인천', value: 'incheon', group: '수도권' },
  { label: '부산', value: 'busan', group: '경상권' },
  { label: '대구', value: 'daegu', group: '경상권' },
];

const groups = [...new Set(REGIONS.map((item) => item.group))];

<Select
  items={REGIONS}
  name="region"
  value={value}
  onChangeValue={handleChange}
>
  <SelectControl>
    <SelectTrigger>
      <SelectValue placeholder="지역 선택" />
    </SelectTrigger>
  </SelectControl>
  <SelectContent>
    {groups.map((group) => (
      <SelectGroup key={group}>
        <SelectGroupLabel>{group}</SelectGroupLabel>
        {REGIONS.filter((item) => item.group === group).map((item) => (
          <SelectItem
            key={item.value}
            item={item}
          >
            <SelectItemText>{item.label}</SelectItemText>
          </SelectItem>
        ))}
      </SelectGroup>
    ))}
  </SelectContent>
</Select>

단일 선택 편의 훅

value가 항상 string[]이라 단일 선택에서는 value[0]으로 꺼내 써야 합니다. 이 변환을 매번 반복하지 않도록, 커스텀 훅 사용을 권장합니다.

import { useState } from 'react';

export default function useForm({ initial }: { initial?: Record<string, any> } = {}) {
  const [fields, setField] = useState(initial);

  const [selectedValue, selectValue] = useState('');

  /* ... */

  const onSelect = ({ name, value }: {
    name: string;
    value: string[]
  }) => {
    setField({
      ...fields,
      [name]: value,
    });

    selectValue(value[0] || '');
  };

  return {
    fields,
    selectedValue,
    onSelect,
  };
}

Props

Select 파트는 Ark UI Select의 props를 그대로 전달할 수 있습니다. 전체 목록은 Ark UI Select 문서를 확인하세요.

Select

PropTypeDefaultDescription
itemsSelectItemData[]옵션 목록 ({ label, value, disabled?, group? })
namestring폼 필드 이름 (필수)
valuestring[]선택된 값 (제어, 단일 선택도 배열)
onChangeValue({ name, value }: { name: string; value: string[]; }) => void값 변경 콜백 (필수)
multiplebooleanfalse다중 선택 허용
disabledbooleanfalse전체 비활성화
hasErrorbooleanfalse에러 스타일 적용

SelectValue

PropTypeDefaultDescription
placeholderstring값이 없을 때 표시할 문구

SelectTrigger

PropTypeDefaultDescription
hasIndicatorbooleantruechevron 아이콘 자동 렌더 여부

SelectItem

PropTypeDefaultDescription
itemSelectItemData해당 옵션 데이터 (필수)

구조용 파트로 SelectControl(트리거 래퍼) · SelectContent(옵션 목록 팝오버) · SelectItemText(옵션 라벨) · SelectGroup · SelectGroupLabel이 있습니다.

접근성

  • Ark UI Select 기반으로 listbox 역할·aria 속성·키보드 탐색(↑/↓·타입어헤드·Enter/Esc) 이 기본 동작합니다.
  • 트리거는 팝오버 열림 상태를 aria-expanded로 노출하며, 선택된 옵션은 data-state="checked"로 표시됩니다.
  • disabled 옵션은 포커스·선택되지 않습니다.

토큰 커스터마이징

Select는 아래 시맨틱 토큰을 참조합니다. (테마 참고).

토큰미리보기용도
layout.bg-light트리거·옵션 배경
layout.default-line트리거·목록 테두리
layout.strong-line트리거 hover·open 테두리
layout.bg-issue에러 배경
layout.issue-line에러 테두리
layout.bg-disable비활성 배경
layout.primary-subtle선택·하이라이트 옵션 배경
text.input입력·옵션 텍스트
text.input-softplaceholder 텍스트
text.accent-primary선택·하이라이트 옵션 텍스트
text.disable비활성 텍스트
text.soft그룹 라벨 텍스트