Select
검색 없이 짧은 옵션 목록에서 하나 또는 여러 값을 선택하는 컴포넌트입니다. 선택값 전체를 펼쳐 편집해야 하면 Combobox를 사용합니다.
🚩 Playground
Style, Size, Layout, Content와 State를 조합하고 실제 선택 동작을 확인합니다.
Appearance
Size
Layout
State
Content
None에서는 Placeholder를 표시합니다. 열린 Popup에서 직접 선택해도 값이 바뀝니다.
Elements
Behavior
Trigger를 열고 방향키로 Option을 이동한 뒤 선택합니다. 검색어 입력은 제공하지 않습니다.
Anatomy
Select를 구성하는 실제 요소와 문서에서 사용하는 명칭입니다.
Select structure
Group A
Group B
- 1.Trigger container
- 2.Value / Placeholder
- 3.Trigger icon
- 4.Popup
- 5.Group
- 6.Group label
Select는 Trigger와 Popup로 구성됩니다. Content 안의 관련 Option은 Group으로 묶으며, Group 사이에만 Divider가 표시됩니다.
- Trigger:
- 현재 선택값 또는 Placeholder를 표시하고, 누르면 Popup를 엽니다. 선택적인 Leading element와 열림 상태를 나타내는 Chevron을 포함할 수 있습니다.
- Popup:
- Trigger에 이어져 Option 목록을 보여주는 영역입니다. Trigger와 같은 너비와 좌우 시작점을 사용합니다.
- Group:
- 관련 있는 Option을 묶는 단위입니다. Group label로 제목을 표시하며 Group 사이에는 Divider를 자동으로 배치합니다.
Option anatomy
- 1.Option
- 2.Leading element(Optional)
- 3.Item label
- 4.Item description(Optional)
Option은 Popup 안에서 선택할 수 있는 하나의 항목입니다. 같은 목록에서 Leading element를 사용하면 모든 Option에 같은 영역을 유지하며, 선택 상태와 Indicator는 Properties · State에서 확인합니다.
- Option:
- Item label을 필수 정보로 사용하고, 필요할 때 Leading element와 Item description으로 내용을 보완합니다.
- Leading element:
- Option 내용 앞에 표시하는 선택 요소입니다. 현재는 Remix Icon으로 검수하며 Label과 Description 전체 블록을 기준으로 수직 중앙 정렬합니다.
- Item description:
- Label 아래에서 추가 정보를 설명하는 선택 요소입니다. Trigger의 선택값에는 표시하지 않습니다.
Properties · Style
Outline과 Filled를 지원하며 모든 Form control의 공통 기본값인 Outline을 사용합니다.
Outline · Default
Filled
Properties · Size
Input과 Button이 공유하는 공통 Control Height를 사용합니다.
Small · 32px
Regular · 36px · Default
Large · 40px
XLarge · 48px
Properties · Layout
부모 너비를 채우는 Fill과 명시한 너비를 유지하는 Fixed를 지원합니다.
Fill · Default
Fixed
Properties · Selection
단일 선택이 기본이며, 다중 선택 결과는 Trigger 한 줄 안에서 대표값과 나머지 개수 또는 전체 개수로 요약합니다.
Single · Default
Multiple · Representative + remaining
Multiple · Total count
Properties · State
Select Trigger 상태와 열린 Content에서 선택된 Option의 표현을 구분해 확인합니다.
Default
Focus
Invalid
Disabled
Read only
Selected option
Properties · Options
평면 Option과 Group을 지원하며 Option에는 선택적인 Leading element와 Description을 둘 수 있습니다.
Flat options
Grouped options
Leading element
Description
Interaction
현재 구현에서 확인되는 열림, 키보드와 선택 동작입니다.
Open / Close
Trigger를 누르면 Popup가 열리고, 바깥 영역을 누르거나 Escape를 누르면 닫힙니다.
Keyboard
방향키로 Option을 이동하고 Enter 또는 Space로 선택하며 Escape로 닫습니다.
Single selection
Option을 고르면 Content가 닫힙니다. 선택한 Option을 다시 골라도 선택이 해제되지 않습니다.
Multiple selection
Option을 고른 뒤에도 Content가 열린 상태로 유지됩니다. 선택한 Option을 다시 고르면 선택이 해제됩니다.
Content height
Content가 사용할 수 있는 높이와 Option 목록의 스크롤 규격입니다.
Content fits
Scrollable content
- Popup은 주변에 남은 공간과 320px 중 작은 높이로 제한됩니다.
- Option 목록이 제한보다 짧으면 Option을 담는 높이만큼만 열립니다.
- Option 목록이 현재 최대 높이인 248px을 넘으면 Content 안에서 스크롤합니다.
- 스크롤 가능한 목록임을 알 수 있도록 마지막 Option의 일부가 Content 하단 경계에서 보이게 합니다.
Guidelines
현재 확정된 Select의 사용 및 콘텐츠 작성 규칙입니다.
Field의 Label과 함께 사용하기
지역
Do
선택한 값의 의미를 알 수 있도록 Field Label을 함께 사용하세요.
Don’t
Placeholder로 Field Label을 대신하지 마세요.
- Select는 항상 Field Label과 함께 사용합니다.
- Placeholder는 값을 선택하면 사라지므로 선택 이후에도 남는 Label을 대신할 수 없습니다.
Leading element 일관되게 사용하기
공개 범위
공개 범위
Do
같은 Group 안에서는 Leading 영역을 동일하게 유지하세요.
Don’t
같은 Group 안에서 일부 Option에만 Leading element를 넣지 마세요.
- Leading element는 Option을 빠르게 구분하도록 돕습니다.
- Group마다 다른 요소를 사용할 수 있지만 같은 Group 안에서는 Leading 영역과 Label 시작점을 맞춥니다.
Item description 배치하기
Do
Item description은 Content 안에만 표시하세요.
Don’t
Trigger에는 Item description을 넣지 마세요.
- Item description은 Option을 구분하는 보조 정보입니다.
- Trigger에는 선택된 Label만 표시해 한 줄 높이를 유지합니다.
옵션 순서 정하기
- 목록의 순서는 사용자가 옵션을 훑는 흐름에 맞춥니다. 사용 빈도, 크기, 시간처럼 그 목록이 이미 가지고 있는 기준 하나를 골라 일관되게 정렬합니다.
- 나머지와 성격이나 정렬 기준이 다른 옵션은 Group으로 묶어 경계를 드러냅니다.
Item label 작성하기
Do
Item label은 명사형으로 짧고 명확하게 작성하세요.
Don’t
동작이나 부연 설명을 Item label에 넣지 마세요.
- 같은 목록에서는 어휘, 길이와 문장 톤을 일관되게 유지합니다.
- Label이 길다면 먼저 줄이고, 필요한 부연 정보는 Item description으로 분리합니다.
Placeholder 작성하기
Do
어떤 종류의 값을 고르는지 알 수 있게 작성하세요.
Don’t
값의 종류를 알 수 없는 막연한 표현은 쓰지 마세요.
- Placeholder는 선택값이 없을 때 Trigger에 표시됩니다.
- Field label이 있다면 같은 문구를 Placeholder에 그대로 반복하지 않습니다.
- 문장이 아니라 「지역 선택」처럼 키워드로 씁니다. 한 줄 Control 안에서 문장은 잘리기 쉽고, 값이 채워지면 사라지는 문구에 존댓말 어미를 붙일 만큼의 자리가 없습니다.
선택 방식과 개수 안내하기
- Field label을 통해 한 개를 고르는지 여러 개를 고르는지 알 수 있게 안내합니다.
- 선택할 수 있는 개수가 정해져 있다면 Label에 함께 표시합니다. 예: ‘담당자 선택 (1명)’, ‘관심 지역 선택 (최대 3개)’
단일 선택에서 ‘없음’을 답으로 받기
- 단일 선택은 선택한 Option을 다시 눌러도 해제되지 않습니다. ‘없음’이 유효한 답이라면 이를 나타내는 Option을 목록에 둡니다.
- ‘없음’을 선택하면 응답을 완료한 것으로 처리합니다. 필수 Field에서도 검증을 통과시켜 미응답과 ‘없음’이라는 응답을 구분합니다.
- Item label에는 질문의 핵심을 포함합니다. Field label이 ‘담당자’라면 ‘해당 없음’ 대신 ‘담당자 없음’으로 작성합니다.
- 다른 Option과 성격이 다르므로 목록의 처음이나 마지막에 별도 Group으로 분리합니다.
다중 선택 사용하기
- 다중 선택에서는 다른 Option의 선택 상태를 바꾸는 Option을 두지 않습니다.
- 따라서 나머지 선택을 모두 바꾸는 ‘전체 선택’이나 ‘없음’ Option을 추가하지 않습니다. 하나의 Option이 다른 Option의 선택 상태까지 바꾸면 동작을 예측하기 어렵습니다.
- 전체 선택이 자주 쓰이는 Field라면 Checkbox의 부모-자식 Checkbox를 활용합니다.
다중 선택 표출하기
선택값을 개수로 요약합니다. 두 가지 형태 중 목록의 성격에 맞는 쪽을 고릅니다.
대표 값과 나머지 개수
전체 개수
- 대표 값과 나머지 개수: 가장 먼저 고른 값을 남기고, 그 값을 뺀 나머지 개수를 표시합니다. 세 개를 골라 맨 처음 고른 하나를 보여준다면 표시하는 숫자는 2입니다. 무엇을 골랐는지 단서가 남으므로 값마다 뜻이 뚜렷이 다른 목록에 적합합니다.
- 전체 개수: 값을 보여주지 않고 고른 전체 개수를 표시합니다. 값들이 서로 동등해서 하나만 보여주는 것이 도움이 되지 않는 목록이나, 너비가 좁아 대표 값을 보여주기 어려운 목록에 적합합니다.
- 선택값 전체를 펼쳐 확인하고 개별 수정해야 한다면 Combobox를 사용합니다.
Property map
현재 구현에서 확인되는 Property와 허용 값을 정리합니다.
| Property | Values | Default | Status |
|---|---|---|---|
| component | Select | MultiSelect | Select | Confirmed |
| variant | outline | filled | outline | Confirmed |
| controlSize | small | regular | large | xlarge | regular | Confirmed |
| layout | fill | fixed | fill | Confirmed |
| value | string | null / string[] | null / [] | Confirmed |
| valueDisplay | summary | count (Multi only) | summary | Confirmed |
| options | groups | SelectOption[] | SelectOptionGroup[] | one required | Confirmed |
| option elements | leadingElement? | description? | none | Confirmed |
| state | default | focus | invalid | disabled | readOnly | default | Confirmed |
명세 문서
docs/system/components/select.md
Select
packages/ui/src/select.tsx
검색 없이 짧은 옵션 목록에서 하나 또는 여러 값을 선택하는 한 줄 Form control이다. 동작은 Base UI Select를 기반으로 하고 Trigger 외관은 Combobox와 공용 selectionTriggerVariants recipe를 공유한다.
역할과 경계
- Select는 미리 정의된 짧은 목록에서 하나 또는 여러 값을 선택한다.
- Popup 내부 Search input을 제공하지 않는다.
- 다중 선택 결과는 Trigger 한 줄에서 대표값과 나머지 개수 또는 전체 개수로 요약한다.
- 선택 결과를 접어 둔다. 선택한 값을 계속 보면서 개별로 빼야 하면 Combobox를 사용한다. Combobox는 선택값 Container를 항상 표시하며 검색·삭제·편집, 전체 선택과 2-depth 탐색을 담당한다.
- 선택지를 화면에 펼쳐 놓고 설명이나 시각 정보를 계속 비교해야 하면 Select Box를 사용한다.
- Label, Helper text와 Error message는 별도 Field가 소유하며 그 구조와 API는 field.md에 있다.
Anatomy · Select structure
- Trigger container: 현재 값을 표시하고 Popup을 여는 실제 Trigger
- Value / Placeholder
- Trigger icon: Remix
RiArrowDownSLine - Popup: Base UI의
Popupprimitive를 사용 - Group: 관련 Option을 묶는 선택적 단위
- Group label
Select는 Trigger와 Popup로 구성된다. Content 안의 관련 Option은 Group으로 묶으며 Group 사이에만 Divider를 표시한다. Trigger는 현재 선택값 또는 Placeholder, 선택적인 Leading element와 열림 상태를 나타내는 Chevron을 포함한다. Popup는 Trigger와 같은 너비와 좌우 시작점을 사용한다.
Anatomy · Option
- Option
- Leading element: Trigger와 Option에 표시할 수 있는 선택 요소
- Item label
- Item description: 열린 Content에서만 표시하는 선택 요소
Selected indicator는 구조 설명에서 제외하고 Properties · State의 Selected option 사례에서 확인한다.
Option은 Popup 안의 선택 항목이다. 같은 목록에서 Leading element를 사용하면 모든 Option에 같은 영역을 유지하며, Leading element는 Label과 Description 전체 블록을 기준으로 수직 중앙 정렬한다. Item description은 Trigger 선택값에 표시하지 않는다.
Properties
variant:outline | filled, 기본값outlinecontrolSize:small | regular | large | xlarge, 기본값regularlayout:fill | fixed, 기본값fillfixedWidth: Fixed에서 사용하는 CSS widthoptions: 평면{ value, label, disabled?, leadingElement?, description? }[]groups:{ label, options }[].options와groups중 하나만 전달leadingElement: 선택값에 별도 Leading element가 없을 때 Trigger에 표시하는 fallback 요소value | defaultValue: 선택한 Option value 또는nullMultiSelect.value | defaultValue: 선택한 Option value 배열MultiSelect.valueDisplay:summary | count, 기본값summaryMultiSelect.selectedSummaryLabel: 대표값과 나머지 개수의 언어별 표기MultiSelect.selectedCountLabel: 선택한 전체 개수의 언어별 표기placeholder: 선택값이 없을 때 표시하는 문자열disabled: Select 전체 상호작용 제한readOnly: 현재 값 변경 제한invalid: 오류 상태 표시required,name: native form 연결 속성ariaLabel: Trigger의 접근 가능한 이름이며 필수다
Control height는 interaction.md의 「공통 Control Height」를 따른다. Outline Disabled에서도 외곽 border를 유지한다.
Behavior
Interaction
- Trigger를 누르면 Popup가 열린다.
- 방향키로 Option을 이동하고 Enter 또는 Space로 선택한다.
- Select는 Option을 선택하면 값이 변경되고 Popup가 닫힌다.
- MultiSelect는 이미 선택한 Option을 다시 고르면 해제하며, 여러 값을 이어서 고를 수 있도록 선택 후에도 Popup를 열린 상태로 유지한다.
- Escape 또는 외부 클릭으로 Popup를 닫는다.
- Disabled Option은 선택할 수 없다.
Selection display
- MultiSelect의
summary는 가장 먼저 선택한 대표값과 그 값을 제외한 나머지 개수를 표시한다. - MultiSelect의
count는 대표값 없이 선택한 전체 개수만 표시한다. - 선택된 Option은 별도 배경과 Label 색상 변경 없이 Neutral label의 Medium 굵기와
oe-brand-foreground-primaryCheck indicator로 표시한다.
Content structure
- Group을 사용하면 Group label과 Option 묶음을 표시하고 Group 사이에 Divider를 자동 배치한다.
- Popup는 Trigger와 같은 너비와 좌우 시작점을 사용한다. Base UI의 선택 Item 중심 정렬은 사용하지 않는다.
- Leading element에는 Icon, Badge, Avatar처럼 Option을 식별하는 요소를 둔다. 한 줄 Control의 높이를 바꾸지 않는 크기를 유지하고, 같은 목록에서는 종류를 섞지 않는다.
- Leading element를 사용하면 Trigger와 Popup의 Leading 열 시작점을 동일하게 맞춘다. Option의 좌우 여백은
controlSize를 따라가며 List가 가진 여백만큼을 뺀 값을 사용한다. - Trigger의 Leading element는 Select의
leadingElement를 사용한다. 어떤 Option을 몇 개 골랐는지에 따라 바뀌지 않는다. Option의leadingElement는 Popup 안의 해당 Option에만 표시한다. - 같은 Group에서 Leading element를 사용하면 모든 Option에 같은 Leading 영역을 제공한다. Group마다 다른 요소를 사용할 수 있지만 같은 Group 안의 일부 Option만 Leading element를 생략하지 않는다.
- Item description은 열린 Content에서 Label 아래에만 표시하고 Trigger 선택값에는 포함하지 않는다.
Content height
- Popup은 주변에 남은 공간과 320px 중 작은 높이로 제한된다. Option 목록이 248px보다 짧으면 Option을 담는 높이만큼만 열리고, 이를 넘으면 최대 248px에서 Content 안쪽 세로 스크롤을 사용한다. 마지막 Option의 글자를 일부 읽을 수 있을 만큼 Popup의 하단 경계에서 노출해 스크롤 가능성을 알린다. 이 경계에는 별도 하단 내부 여백을 두지 않는다. Scrollbar 공간은 유지하고 thumb은 Hover에서 표시한다.
Guidelines
- Select는 항상 Field Label과 함께 사용해 선택 이후에도 값의 의미를 알 수 있게 한다. 값을 선택하면 사라지는 Placeholder로 Field Label을 대신하지 않는다.
- 다중 선택 결과는 목록 성격에 따라 대표값과 나머지 개수 또는 전체 개수로 요약한다.
- 대표값과 나머지 개수는 값마다 의미가 뚜렷해 첫 선택값이 단서가 되는 목록에 사용한다. 표시 개수에는 대표값을 제외한다.
- 전체 개수는 값들이 서로 동등하거나 Trigger 너비가 좁아 대표값이 도움이 되지 않는 목록에 사용한다.
- 선택값을 모두 펼쳐 보고 개별 삭제·검색·편집해야 하면 Combobox를 사용한다.
- 선택 항목의 설명이나 시각 정보를 계속 노출해 비교해야 하면 Select Box를 사용한다. 짧은 키워드를 유연하게 나열해 선택하거나 필터링하면 Chip을 사용한다.
- 다중 선택에서는 다른 Option의 선택 상태를 바꾸는 Option을 두지 않는다.
- 나머지 선택을 모두 바꾸는
전체 선택이나없음Option을 추가하지 않는다. 전체 선택이 자주 필요하면 Checkbox의 부모-자식 패턴을 사용한다. - Do: Leading element는 Group마다 다르게 구성할 수 있지만 같은 Group 안에서는 모든 Option에 동일한 Leading 영역을 유지한다.
- Don't: 같은 Group 안의 일부 Option에만 Leading element를 표시하지 않는다.
- Do: Trigger에는 선택된 Item label만 표시하고 Item description은 열린 Content에서 확인한다.
- Don't: Item description을 Trigger에 함께 표시해 한 줄 Control의 높이와 정보 위계를 바꾸지 않는다.
- 목록은 사용자가 훑는 흐름에 맞춰 사용 빈도, 크기, 시간처럼 이미 존재하는 기준 하나로 일관되게 정렬한다.
- 성격이나 정렬 기준이 다른 Option은 별도 Group으로 묶어 경계를 드러낸다.
- Item label은 명사형으로 짧고 명확하게 작성하며 같은 목록에서 어휘, 길이와 톤을 일관되게 유지한다. 부연 정보는 Item description으로 분리한다.
- Placeholder는 고를 값의 종류를 알 수 있게 작성하며 Field label을 그대로 반복하지 않는다. 문장이 아니라
지역 선택처럼 키워드로 쓴다. 한 줄 Control 안에서 문장은 잘리기 쉽고, 값이 채워지면 사라지는 문구에 존댓말 어미를 붙일 만큼의 자리가 없다. Input Button의 같은 규칙과 기준을 공유한다. - Field label을 통해 한 개를 고르는지 여러 개를 고르는지 안내한다. 선택 가능 개수가 정해져 있다면 Label 안에 괄호로 넣지 않고 field.md의
rightDescription에1명,최대 3개처럼 표시한다. - 단일 선택에서
없음이 유효한 답이라면 이를 명시하는 Option을 제공하고 선택 완료로 처리한다. Item label은담당자 없음처럼 질문의 핵심을 포함하며, 목록 처음이나 마지막의 별도 Group으로 분리한다.
현재 정의하지 않은 항목
- Option 안에서 따로 조작되는 요소를 Leading element로 허용하는 정책
- Trailing element와 Option image
- Clear button
- 검색과 자유 입력
- 모바일에서 별도 Bottom Sheet로 전환하는 정책
- Popup 최대 높이의 제품별 기준