Select
옵션 중 하나를 선택하는 콤보박스/리스트박스 컴포넌트.
트리거 라벨이 현재 선택값을 반영해야 할 때 사용.
Mobile
모바일에서 Select는 옵션 목록을 BottomSheet로 표시한다. 트리거 자체의 형태(라벨 + chevron)는 동일하나, 탭하면 popover가 아닌 시트가 슬라이드 업한다.
- 컨테이너 anatomy(detents·grabber·swipe-to-dismiss·safe area)는 Dialog → Mobile Presentation 참조
- 옵션 행: 세로 스택, 각 행 ≥44pt (터치 타겟), 좌측 라벨 + 우측 selected indicator (checkmark)
- 옵션 수가 8개를 초과하면 detent를
[auto, 1]로 설정해 사용자가 시트를 끌어올려 확장할 수 있게 함 - 검색이 필요할 때는
MultiSelect또는 combobox 패턴 사용 - 헤더 영역에 현재 라벨(예: "언어 선택") 표시. dismiss는 옵션 선택 시 자동 + 시트 외부 영역 탭
Preview
States
closed · open · selected · disabled
Sizes
지원 사이즈: sm · md · lg
Default: md
Trigger는 Input과 동일 스케일 사용:
| Size | Height | px | py | Text token | Primary use | Mobile (≥44pt hit area) |
|---|---|---|---|---|---|---|
| sm | 32px | 10 | 6 | label3Medium 12/18 Medium | Dense filter, inline select | hitSlop으로 hit area 44pt 확장 필수 |
| md | 40px | 12 | 10 | label3Medium 12/18 Medium | Default (web). Form | hitSlop 4pt 권장 |
| lg | 48px | 16 | 12 | label3Medium 12/18 Medium | Mobile primary form (default) | ✓ 기본 충족 |
Item (dropdown 내부, web): h-7 (28px), label3Medium 12/18 Medium. 옵션 목록은 trigger보다 더 조밀하게 표시해 긴 목록을 빠르게 스캔할 수 있게 한다. 단일 선택이므로 selected indicator 없음.
| Item context | Row height (web) | Row height (mobile) | 비고 |
|---|---|---|---|
| Dropdown (popover) | h-7 (28px) | — (모바일은 BottomSheet 변환) | Web 한정 |
| BottomSheet option | — | ≥44pt | 모바일 표준 — Select Mobile |
Disabled Item (맨 아래 안내 텍스트)
옵션 목록 마지막에 "더 많은 언어가 곧 지원될 예정이에요!"처럼 선택 불가능한 안내 텍스트를 넣을 때는 disabled prop을 사용한다. data-[disabled] 상태에서 text-stone-400으로 표시되어 일반 항목과 구분된다.
<SelectItem value="more" disabled>
더 많은 언어가 곧 지원될 예정이에요!
</SelectItem>disableditem은 클릭/포커스 불가 (pointer-events-none)- 텍스트 색상:
stone-400(muted) - 별도 separator 없이 자연스럽게 섞어서 배치
Scroll & Max Visible Items
항목이 많을 때 드롭다운 길이를 제한하고 스크롤을 제공한다.
| Rule | Value | Rationale |
|---|---|---|
| 최대 표시 항목 수 | 6개 | 6 × 28px = 168px. 그 이상은 스크롤 |
| max-height (Content) | 176px | 항목 6개 + 상하 padding p-1 포함 |
| 스크롤 방식 | 네이티브 우측 스크롤바 (overflow-y-auto) | Chevron 버튼 없음, 자연스러운 스크롤 |
| 기본 열림 방향 | side="bottom" (아래 우선) | 트리거 아래에서 열리는 것이 자연스러움 |
- 항목이 6개 이하일 때는 스크롤바 미표시 (내용이 max-height 이내)
- 항목이 6개 초과 시 우측 네이티브 스크롤바가 자동 표시됨
position="popper"+ Radix collision detection이 공간 부족 시 위로 뒤집는 것을 방지하기 위해max-h-[176px]을 항상 적용
Token Mapping
| Token | Value | Description |
|---|---|---|
| trigger height (sm) | h-8 (32px) | TextInput size="sm"과 동일 |
| trigger height (md) | h-10 (40px) | Default |
| trigger height (lg) | h-12 (48px) | |
| trigger border | 1px solid stone-200 | Input 패턴 |
| focus ring | 없음 (focus:outline-none) | 클릭 시 ring 미표시 |
| placeholder | stone-400 | |
| text (trigger + item) | label3Medium | Trigger와 Item 동일 크기 (12/18 Medium) |
| content bg | white | |
| content border | 1px solid stone-200 | |
| content radius | rounded-lg | |
| content shadow | shadow-md | |
| content padding | p-1 | |
| content sideOffset | 4px | trigger와 content 간격 |
| item hover | stone-50, rounded-md inset | |
| item height | h-7 (28px) | |
| disabled item text | stone-400 | data-[disabled]:text-stone-400 |
| chevron icon | stone-400, h-3.5 w-3.5 | strokeWidth 2 |
Props
SelectTrigger는 size prop을 지원한다. Input의 size prop과 동일한 스케일이므로 같은 Row에 혼합 배치할 때 크기를 맞출 수 있다.
| Prop | Type | Default | Description |
|---|---|---|---|
size | "sm" | "md" | "lg" | "md" | Trigger 높이 (sm=32px, md=40px, lg=48px) |
Usage
{
/* Default (md, 40px) — 일반 폼 */
}
<Select value={plan} onValueChange={setPlan}>
<SelectTrigger>
<SelectValue placeholder="플랜 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="monthly">월간 ($10)</SelectItem>
<SelectItem value="yearly">연간 ($100)</SelectItem>
<SelectItem value="team">팀 ($25/user)</SelectItem>
</SelectContent>
</Select>;
{
/* sm (32px) — Item 행 안에 인라인으로 배치, TextInput size="sm"과 동일 높이 */
}
<Select value={lang} onValueChange={setLang}>
<SelectTrigger size="sm" className="w-[200px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="ko_KR">한국어 / Korean</SelectItem>
<SelectItem value="en_US">English / English (US)</SelectItem>
</SelectContent>
</Select>;A11y
role="combobox"+role="listbox"+role="option"자동- Label 연결 필수 (
htmlFor) - 키보드 탐색 (Arrow, Home/End, type-to-search)
검색 가능한 콤보박스 또는 다중 선택이 필요하다면 MultiSelect 컴포넌트를 사용하세요.
| 상황 | 컴포넌트 |
|---|---|
| 옵션 5–20개, 선택값이 트리거에 표시 | Select (기본) |
| 옵션 20개 이상 또는 검색 필요 | MultiSelect |
| 멀티 선택 + 검색 | MultiSelect |
| 선택 결과가 트리거에 반영 안 됨 | DropdownMenu |
Table / Row 안에서 사용할 때 (Ghost variant)
테이블 행이나 List Item 내부에 Select를 배치할 경우, 기본 border가 주변 행과 시각적으로 충돌한다. 이때는 border와 배경을 제거한 ghost 스타일을 사용한다.
| Variant | border | bg | 주 사용처 |
|---|---|---|---|
default | 1px solid stone-200 | white | 폼, 필터 영역 |
ghost | 없음 | 투명 | 테이블 행, List Item 내 인라인 역할 선택 |
- hover 시
bg-stone-50유지 — 인터랙티브임을 시각적으로 전달 - paid 옵션 badge:
caption1Regular text-stone-400으로$표시 —Select.ItemText바로 뒤에 인라인 배치 ghost트리거는 행 전체 클릭 영역과 겹치지 않도록 클릭 이벤트를 분리해서 배치
Do / Don't
- Do: 선택값이 UI에 표시되어야 할 때 사용
- Do: 옵션 5–20개에 적합
- Don't: 2개 옵션 토글은
Switch또는ToggleGroup - Don't: 선택 결과가 트리거에 반영되지 않는다면
DropdownMenu사용 - Don't: 검색이나 다중 선택이 필요하다면
Select대신MultiSelect사용
