Select
목록에서 값 하나를 고릅니다. 트리거는 TextField의 셸에 셰브런을 얹은 것이고, 이것은 의도된 것입니다.
tsx
import { Select } from 'neba';
<Select
label="리전"
placeholder="리전을 고르세요"
items={[
{ value: 'icn', label: '서울' },
{ value: 'nrt', label: '도쿄' }
]}
/>;Props
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| variant공통 | 'solid' | 'outline' | 'text' | 'outline' | 표면의 무게. TextField와 같은 셸이므로 폼 안에서 두 컨트롤이 구분되지 않습니다 |
| size공통 | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | 높이와 타입 스케일. Button·TextField와 같은 높이입니다 |
| color공통 | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'primary' | 의미론적 색 역할. 표면은 흰색이므로 가장자리와 포커스 링에만 나타납니다 |
| density공통 | 'default' | 'compact' | 'default' | 여백만 바꿉니다. 높이와 글자 크기는 그대로 |
| elevation공통 | 0 | 1 | 2 | 3 | 0 | 트리거의 그림자 깊이. 팝업은 자기 그림자를 따로 가집니다 |
| items * | readonly SelectOption[] | — | 옵션 목록. { value, label?, disabled? } 배열입니다 |
| value | string | number | null | — | 선택된 값. onValueChange와 함께 제어 컴포넌트로 씁니다 |
| defaultValue | string | number | null | — | 초기 선택 값 |
| onValueChange | (value: string | number | null) => void | — | 선택이 바뀔 때 |
| placeholder | ReactNode | — | 아무것도 고르지 않았을 때 트리거에 보이는 내용 |
| label | ReactNode | — | 컨트롤과 연결되는 라벨. Base UI Field가 묶어 줍니다 |
| description | ReactNode | — | 보조 설명 |
| error | ReactNode | — | 오류 메시지. 값이 있으면 invalid 상태도 함께 켜지고 색 계열이 danger로 넘어갑니다 |
| invalid | boolean | !!error | 메시지 없이 invalid만 켭니다. 외부 폼 라이브러리가 유효성을 가질 때 |
| startIcon | ReactNode | — | 값 앞에 놓이는 내용 |
| fullWidth | boolean | false | 컨테이너 너비만큼 확장 |
| name | string | — | 폼 제출 시의 필드 이름 |
| readOnly | boolean | false | 값은 보이지만 바꿀 수 없음. 색과 가장자리는 유지한 채 채도만 빠집니다 |
| disabled | boolean | false | 사용 불가. 색 계열을 버리고 중립 회색이 됩니다 |
옵션은 데이터입니다
조합할 <Select.Option>은 없습니다. 호출하는 쪽이 가진 것은 거의 항상 이미 배열이고, 목록은 팝업이 한 번도 열리기 전에 트리거가 읽을 수 있어야 합니다 — value="icn"에 대해 첫 페인트부터 서울이 보이는 것은 그래서입니다.
ts
interface SelectOption {
value: string | number;
label?: React.ReactNode; // 생략하면 value 자체
disabled?: boolean;
}값은 문자열과 숫자이고, 임의의 객체가 아닙니다. select는 폼 컨트롤이고 그 값은 제출되는 것입니다. 식별자만 여기 두고 객체는 반대편에서 찾으세요.
예시
Variant
TextField와 같은 세 가지 무게를, 같은 셸 위에 그립니다. select만 주변 필드와 높이·반경·색이 다른 폼은 디자인된 것이 아니라 조립된 것처럼 보입니다.
크기
상태
팝업
팝업은 이 라이브러리에서 유일하게 정말로 떠 있어야 하는 표면이므로, 다른 모든 것과 달리 요청하지 않아도 그림자를 답니다. 호버 없이 도달할 수 있는 최대치인 3단계입니다.
팝업은 포털을 통해 <body> 끝에 렌더링되며, 그 말은 앱이 CSS 리셋을 한정해 둔 서브트리를 벗어난다는 뜻입니다. positioner에 neba-portal 클래스가 붙는 것은 정확히 그 경우를 위한 것으로, 스타일이 아니라 리셋을 걸어 둘 고리입니다. Tailwind Preflight를 전역으로 적용한 앱이라면 아무것도 하지 않아도 됩니다.
접근성
- 팝업의 위치 계산과 뒤집힘, 포커스 트랩, 타이프어헤드, 폼 제출을 가능하게 하는 숨은 input은 모두 Base UI가 담당합니다.
label이 접근성 이름이 되고, 트리거는combobox입니다.- 비활성 옵션은 목록에 남은 채
aria-disabled로 보고됩니다 — 옵션은 존재하고, 다만 고를 수 없을 뿐입니다.