# Select 정해진 목록에서 하나를 고르게 하는 선택 컴포넌트입니다. 목록은 같은 자리에서 펼쳐집니다. > 원본: `03_ASSET_GUIDE/catalog/select.js` · CSS: `03_ASSET_GUIDE/styles/elements/select.css` > kind `element` · category `selection` · status `stable` · specSource `measured` --- ## 1. 구성 닫혀 있을 때 보이는 **Trigger** 와, 열렸을 때 나타나는 **Listbox** 두 부분으로 나뉩니다. Option 은 아이콘, 라벨, 설명, 선택 체크를 가질 수 있습니다. ``` ┌──────────────────────────────────┐ │ 최신 순 ⌄ │ ① Trigger ② Value ③ Caret └──────────────────────────────────┘ ┌──────────────────────────────────┐ │ 정렬 │ ④ Listbox ⑤ Group label │ ┌──────────────────────────────┐ │ │ │ 최신 순 ✓ │ │ ⑥ Option + Check │ └──────────────────────────────┘ │ │ 인기 순 │ └──────────────────────────────────┘ ``` - **Trigger** — 현재 값과 Caret 을 보여주는 버튼입니다. - **Caret** — 열리면 180° 회전해 상태를 알립니다. - **Listbox** — 선택지 목록. 트리거 바로 아래에 붙습니다. - **Group label** — 선택지가 많을 때 묶음의 이름입니다. - **Option** — 라벨은 필수, 아이콘과 설명은 선택입니다. - **Check** — 현재 선택된 항목에만 나타납니다. | # | 요소 | 필수 | 클래스 | 설명 | |---|---|---|---|---| | — | Root | 필수 | `atom-select` | Trigger 와 Listbox 를 감쌉니다. `data-state` · `data-invalid` · `data-disabled` 가 여기에 붙습니다. | | ① | Trigger | 필수 | `atom-select__trigger` | 현재 값과 Caret 을 보여주는 버튼입니다. | | ② | Value | 필수 | `atom-select__value` | 선택된 값 또는 Placeholder. | | ③ | Caret | 필수 | `atom-select__caret` | 열리면 180° 회전해 상태를 알립니다. | | ④ | Listbox | 열렸을 때 | `atom-select__listbox` | 선택지 목록. 트리거 바로 아래에 붙습니다. | | ⑤ | Group label | 선택 | `atom-select__group-label` | 선택지가 많을 때 묶음의 이름입니다. | | ⑥ | Option | 필수 | `atom-select__option` | 라벨은 필수, 아이콘과 설명은 선택입니다. | | — | Option icon | 선택 | `atom-select__option-icon` | 20px 아이콘. | | — | Option text | 필수 | `atom-select__option-text` | Label 과 Description 을 세로로 쌓습니다. | | — | Option label | 필수 | `atom-select__option-label` | 500 굵기 라벨. | | — | Option description | 선택 | `atom-select__option-description` | 12px 설명. | | — | Check | 필수 | `atom-select__option-check` | 현재 선택된 항목에만 나타납니다. | | — | Empty | 빈 목록일 때 | `atom-select__empty` | 선택지가 없을 때 Listbox 안의 한 줄 안내. | --- ## 2. 종류 / 크기 ### 열린 상태 선택지가 8개를 넘으면 그룹으로 묶고, 선택할 수 없는 항목은 목록에서 빼는 대신 `aria-disabled` 와 이유를 함께 남깁니다. Listbox 는 Trigger 아래 6px 간격으로 붙고, 자체 패딩 6px 안에 Option 이 놓입니다. Option 행 높이는 폰트 14px 에 패딩 9px 10px 입니다. Option 은 네 조각으로 구성됩니다 — Icon(20px) · Label(500 굵기) · Description(12px) · Check(18px). Check 는 `aria-selected="true"` 일 때만 `opacity: 1` 이 됩니다. > [!WARNING] > **Listbox 의 최대 높이가 없습니다** > `max-height` 도 `overflow` 도 지정되어 있지 않아, 선택지가 많으면 Listbox 가 화면 밖까지 > 늘어납니다. 위치 계산과 함께 최대 높이·내부 스크롤을 정해야 합니다. > 필요한 처리는 `max-height`, `overflow-y: auto`, 아래쪽 공간이 부족할 때 위로 뒤집기 > 세 가지입니다. ### Variant `weak` 가 기본입니다. 같은 폼 안의 [Input](./text-input.md) 이 테두리를 쓰고 있다면, 시각적 통일을 위해 `outline` 을 씁니다. | Variant | Trigger 배경 | 테두리 | |---|---|---| | `weak` (기본) | `--atom-bg-neutral-weak` | 없음 | | `outline` | `--atom-bg-layer` | `--atom-border-default` 1px inset | 기본이 `weak` 인 것이 [Input](./text-input.md)(기본 `outline`)과 반대라는 점에 주의하세요. 두 컴포넌트를 같은 폼에 놓으면 한쪽을 맞춰야 합니다. ### Size 정렬 드롭다운처럼 툴바 안에 들어갈 때는 `small`, 폼 안에서는 `medium` 이상을 씁니다. | Size | Trigger 높이 | 모서리 | 폰트 | Caret | |---|---|---|---|---| | `small` | 40px | 8px | 14px | 16px | | `medium` | 48px | 8px | 15px | 18px | | `large` | 56px | 12px | 16px | 18px | > [!NOTE] > **높이는 컨트롤 공통 토큰입니다** > 40 · 48 · 56px 은 `--atom-control-h-small` · `--atom-control-h` · `--atom-control-h-large` > 이고, [Input](./text-input.md) · [Date Input](./date-input.md) · > [Time Input](./time-input.md) · [Input Button](./input-button.md) 이 같은 값을 > 씁니다. 같은 `size` 를 주면 한 줄에 나란히 놓아도 어긋나지 않습니다. [Field](./field.md) 의 Control 슬롯에 넣을 때는 `--variant_outline` 을 씁니다. 기본 `weak` 는 테두리가 없어서, 테두리를 가진 Input 옆에 서면 같은 줄로 읽히지 않습니다. ### 빈 목록 검색 결과가 없으면 빈 Listbox 대신 무엇을 바꿔야 하는지 알려주는 한 줄을 표시합니다. ### Field 와 함께 라벨·도움말은 [Form Field](./field.md) 로 감싸서 붙입니다. ### 클래스 계약 `styles/elements/select.css` 헤더와 원본 `registry.json` `css.blocks` 기준입니다. 클래스가 없는 값(—)은 기본 스타일 그대로입니다. | Prop | 값 | 클래스 | |---|---|---| | `size` | `small` · `medium` · `large` | `--size_small` · — · `--size_large` | | `variant` | `weak` · `outline` | — · `--variant_outline` | | (값 없음) | `value` 가 없거나 options 에 없음 | `--empty` | 상태 선택자: `[data-state="closed\|open"]` · `[data-invalid]` · `[data-disabled]` · `:hover` · `:focus-visible` · Option 의 `[aria-selected="true"]` · `[aria-disabled="true"]` > `--empty` 는 CSS 에 정의되어 있지만 헤더 주석에는 빠져 있습니다(원본 registry `css-defined-not-declared`). > CSS 에는 `.atom-select__trigger--empty` 도 같은 규칙으로 정의되어 있으나 registry 블록에 없어 구현은 루트의 `--empty` 만 씁니다. --- ## 3. 상태 `closed` 와 `open` 이 기본 상태이고, 여기에 `invalid` 와 `disabled` 가 겹칩니다. 값이 없으면 `--empty` 로 Value 를 흐리게 둡니다. | State | 표현 | 선택자 | |---|---|---| | `closed` | Listbox `display: none` | `[data-state="closed"]` | | `open` | Listbox 노출, Caret 180° 회전 | `[data-state="open"]` | | `hover` | Trigger `filter: brightness(0.96)` | `:hover` | | `empty` | Value 를 `--atom-fg-neutral-subtle` 로 | `--empty` | | `focus` | Trigger 안쪽에 2px `--atom-primary-600` 링 | `:focus-visible` | | `invalid` | Trigger 테두리 2px 오류 색 | `[data-invalid]` | | `disabled` | `opacity: 0.4`, `pointer-events: none` | `[data-disabled]` | --- ## 4. Props | Prop | Type | Default | 필수 | 설명 | |---|---|---|---|---| | `value` | `string` | | | 선택된 항목의 값입니다. | | `placeholder` | `string` | | | 값이 없을 때 Trigger 에 보이는 문구입니다. | | `options` | `Array<{ value, label, description?, icon?, disabled? }>` | | ● | 선택지 목록입니다. | | `size` | `"small" \| "medium" \| "large"` | `"medium"` | | Trigger 높이와 폰트 크기를 결정합니다. | | `variant` | `"weak" \| "outline"` | `"weak"` | | Trigger 의 배경과 테두리 처리입니다. | | `invalid` | `boolean` | `false` | | Trigger 테두리를 오류 색으로 바꿉니다. | | `disabled` | `boolean` | `false` | | 열 수 없게 만듭니다. | | `emptyText` | `string` | `"결과가 없어요"` | | 선택지가 비었을 때 Listbox 에 보일 문구입니다. | | `onValueChange` | `(value: string) => void` | | | 선택이 바뀔 때 호출됩니다. | 구현이 추가로 받는 props (원본 Props 표에 없음 — controlled/uncontrolled 와 재사용을 위한 것): | Prop | Type | Default | 설명 | |---|---|---|---| | `options` 의 그룹 항목 | `{ label: string; options: SelectOption[] }` | | `options` 에 섞어 넣으면 Group label 을 붙여 묶습니다. | | `defaultValue` | `string` | | uncontrolled 일 때 처음 선택된 값입니다. | | `open` · `defaultOpen` · `onOpenChange` | `boolean` · `boolean` · `(open) => void` | `defaultOpen=false` | 열림 상태 controlled / uncontrolled. | | `name` | `string` | | 지정하면 hidden input 으로 값을 폼에 함께 보냅니다. | | `triggerId` | `string` | | Trigger(`