Select
Stable미리 정의한 옵션 중 하나를 선택하는 필드입니다.
Overview
Select는 여러 개의 선택지 중 하나 또는 여러 개의 옵션을 선택할 수 있는 풍부한 드롭다운 컨트롤입니다. Seed Design 표준을 준수하여 아이템별 보조 설명, 접두사 아이콘, 실시간 검색, 다중 선택 태그/요약 모드, 그룹 구분자를 모두 지원합니다.
"seoul"<Select
size="md"
searchable={true}
clearable={true}
placeholder="여행지를 선택하세요"
value={selectedValue}
onValueChange={(val) => setSelectedValue(val)}
options={[
{ value: 'seoul', label: '서울', description: '수도이자 문화 중심지', prefixIcon: <Landmark /> },
{ value: 'busan', label: '부산', description: '대표 해양 도시', prefixIcon: <MapPin /> },
...
]}
/>WAI-ARIA role="combobox" 및 role="listbox" 표준을 준수합니다. 키보드 화살표 키(↑, ↓)로 탐색하고, Enter/Space 키로 선택하며, Esc 키로 드롭다운을 즉시 닫을 수 있습니다. 또한 SSR 환경 및 표준 폼 전송과의 100% 호환성을 보장합니다.
Anatomy
| 번호 | 구성 요소 | 역할 |
|---|---|---|
| 1 | Label | 필드 레이블. htmlId와 aria-labelledby로 연결되는 폼 컨트롤 제목 |
| 2 | Trigger Container | 현재 선택된 값이나 placeholder를 보여주고 클릭/엔터 시 드롭다운을 토글하는 외곽 영역 |
| 3 | Prefix Icon | 트리거 또는 개별 아이템 좌측에 위치하는 문맥 아이콘 |
| 4 | Value / Tags / Summary | 단일 선택된 텍스트, 다중 선택된 개별 제거 가능 태그 칩, 또는 요약 문구 |
| 5 | Clear Button & Chevron | 값 초기화 X 버튼 및 열림 상태에 따라 180도 회전하는 화살표 인디케이터 |
| 6 | Dropdown Popover | 실시간 검색창, 카테고리 헤더, 아이템 목록 및 체크 인디케이터를 포함하는 팝오버 |
| 7 | Helper / Error Message | aria-describedby와 연동되는 가이드라인 또는 유효성 검사 안내 |
Variants
단순 텍스트 옵션부터 메타데이터가 포함된 풍부한 옵션, 검색 필터링 및 다중 선택(태그·요약)까지 용도에 맞는 변형을 제공합니다.
| Variant | 미리보기 (Preview) | 특징 및 권장 적용 맥락 |
|---|---|---|
| 기본 단일 선택 | 한국어 (Korean) | 접두사 아이콘이나 부가 설명 없이 순수 텍스트로 구성된 가장 표준적인 단일 선택 |
| 메타데이터 확장 | 신용/체크카드 | 접두사 아이콘(prefixIcon)과 보조 설명(description)을 함께 노출해 선택 맥락 강화 |
| 실시간 검색 | 도쿄 | 선택지가 많은 경우(10개 이상) 드롭다운 상단 검색창으로 실시간 필터링 제공 |
| 다중 선택 (Tags) | 복수 항목을 개별 태그 칩으로 나열하고 각 칩의 X 버튼으로 즉시 제거 가능 | |
| 다중 선택 (Summary) | 서울 외 2개 | "서울 외 2개"처럼 간결한 요약형 텍스트로 표시하여 좁은 화면이나 모바일에 최적화 |
Sizes
주변 입력 필드 및 버튼과의 조화를 위해 3가지 표준 크기를 제공합니다. 기본값은 md(40px)입니다.
| Size | 높이 (Height) | 미리보기 (Preview) | 권장 적용 맥락 |
|---|---|---|---|
sm | 32px | 한국어 (Korean) | 밀도 높은 데이터 테이블 내부 필터, 좁은 사이드바 패널 제어 |
md | 40px (기본값) | 한국어 (Korean) | 대부분의 표준 폼 양식, 설정 다이얼로그 및 기본 입력 뷰 |
lg | 48px | 한국어 (Korean) | 히어로 검색 폼, 터치 조작 편의성이 최우선인 모바일 인터페이스 |
States
상호작용 및 폼 유효성 검증 상태를 시각적 테두리와 색상으로 명확히 구분합니다.
| 상태 (State) | 미리보기 (Preview) | 동작 및 가이드 |
|---|---|---|
| Default | 한국어 (Korean) | 사용자의 선택을 기다리는 기본 활성 인터랙션 상태 |
| Error | 필수 선택 항목입니다 | 유효성 검증 실패 시 강조 테두리(Red)와 aria-invalid 적용 |
| ReadOnly | 선택값 열람은 허용되나 드롭다운 열림 및 값 수정은 차단 (폼 전송 포함) | |
| Disabled | 한국어 (Korean) | 조작 및 폼 전송에서 완전히 제외되는 비활성화 상태 |
Guidelines
권장해요
옵션이 5개 이상이거나 화면 공간을 절약해야 할 때 Select를 사용합니다.
목록이 10개 이상으로 길어지면 searchable을 활성화하여 빠른 탐색을 지원합니다.
옵션 간 성격이 다를 때는 group 프로퍼티로 논리적으로 분류합니다.
다중 선택 시 모바일 환경이나 좁은 공간에서는 summary 모드를 활용합니다.
피해주세요
선택지가 2~3개 이하로 적고 즉시 비교가 필요할 때는 Select 대신 Radio 또는 Switch를 고려하세요.
placeholder만으로 Label을 대신하지 않습니다. 항상 명확한 레이블을 제공합니다.
선택 가능한 옵션이 없을 때 빈 드롭다운만 방치하지 않고 적절한 안내 문구를 표시합니다.
API
| Prop | Type | Default | Description |
|---|---|---|---|
| options | SelectItem[] | 필수 | value, label, description, prefixIcon, disabled, group을 포함하는 옵션 배열 |
| value | string | string[] | undefined | 현재 선택된 값 (단일: 문자열, 다중: 문자열 배열) |
| defaultValue | string | string[] | undefined | 초기 기본 선택 값 |
| placeholder | string | undefined | 선택된 값이 없을 때 표시할 안내 문구 |
| multiple | boolean | false | 다중 선택 모드 활성화 여부 |
| displayMode | 'tags' | 'summary' | 'tags' | 다중 선택 시 트리거 내 표시 방식 (개별 칩 vs 요약 텍스트) |
| searchable | boolean | false | 드롭다운 상단에 실시간 필터 검색창 노출 여부 |
| clearable | boolean | false | 선택된 값을 한 번에 지울 수 있는 X 버튼 표시 여부 |
| size | 'sm' | 'md' | 'lg' | 'md' | 컨트롤의 높이 및 패딩 크기 |
| prefixIcon | ReactNode | undefined | 트리거 좌측에 표시할 공통 접두사 아이콘 |
| onValueChange | (val: string | string[]) => void | undefined | 선택 값 변경 시 트리거되는 직관적인 값 변경 콜백 |
| onChange | (e: any) => void | undefined | 기존 리액트 이벤트 핸들러와의 완벽한 하위 호환성을 제공하는 콜백 |
Code Example
import { useState } from 'react';
import { Field, Select, type SelectItem } from '@/components/ui';
import { Landmark, MapPin, TreePine } from 'lucide-react';
const destinations: SelectItem[] = [
{ value: 'seoul', label: '서울', description: '수도 및 문화 중심', prefixIcon: <Landmark /> },
{ value: 'busan', label: '부산', description: '해양 관광 도시', prefixIcon: <MapPin /> },
{ value: 'jeju', label: '제주도', description: '힐링 자연 휴양지', prefixIcon: <TreePine /> },
];
export function BookingForm() {
const [selectedCities, setSelectedCities] = useState<string[]>(['seoul']);
return (
<Field
label="희망 여행지"
description="방문하고 싶은 도시를 선택하세요."
required
>
<Select
multiple={true}
displayMode="tags"
searchable={true}
clearable={true}
options={destinations}
value={selectedCities}
onValueChange={(val) => setSelectedCities(val as string[])}
placeholder="도시를 선택하세요"
/>
</Field>
);
}