본문으로 건너뛰기
Dot Design System
v0.3.1

Select

제한된 선택지 중 하나를 공간 효율적으로 고릅니다.

목적

Select는 미리 정해진 후보 중 하나를 선택하게 한다. 현재 값, 사용 가능한 다음 선택, 선택 결과를 한 control 안에서 연결해 form의 복잡도를 줄인다.

선택 기준

  • 후보가 고정되어 있고 한 값만 선택할 때 사용한다.
  • 두세 개의 선택을 계속 비교·전환해야 하면 Tabs나 직접 보이는 control을 검토한다.
  • 후보 검색이 핵심이거나 수백 개라면 Command 기반 선택 구성이 더 적절하다.
  • 여러 값을 동시에 고르는 용도로 사용하지 않는다. 현재 public API는 multi-select를 제공하지 않는다.

구조

Select 아래에 SelectTriggerSelectContent를 둔다. Trigger 안에는 SelectValue를 배치한다. Content 안에서 SelectGroup, SelectLabel, SelectItem, SelectSeparator로 후보를 구성한다. Content는 scroll up/down control과 viewport를 내부에서 자동 구성한다.

외부 Label을 Trigger의 id와 연결해 값의 의미를 제공한다.

변형과 크기

별도 variant나 size prop은 없다. Trigger 기본 높이는 40px이고 전체 너비를 사용한다. Content의 기본 positionpopper이며 Trigger의 너비와 높이를 기준으로 viewport를 맞춘다. 후보가 사용 가능한 높이를 넘으면 세로 scroll이 생긴다.

상태와 동작

Selectvalue, defaultValue, onValueChange, open, onOpenChange 등 기반 root props를 전달한다. Trigger는 placeholder, focus, disabled 상태를 표현한다. Item은 focus, selected, disabled 상태를 지원하고 선택된 항목에는 check indicator가 나타난다.

비동기로 후보를 바꿀 때 현재 값이 여전히 유효한지 확인하고, 값을 조용히 초기화하지 않는다.

콘텐츠

Label은 선택 기준을 설명하고 placeholder는 담당자 선택처럼 아직 값이 없음을 알려준다. Item은 서로 구분되는 짧은 명사를 사용한다. 내부 코드나 축약어만 보여주지 말고 필요한 경우 사용자에게 익숙한 설명을 포함한다.

접근성

Trigger에 보이는 Label을 연결한다. placeholder를 Label 대신 사용하지 않는다. keyboard로 열기, 항목 이동, 선택, 닫기가 가능해야 하며 focus 스타일을 제거하지 않는다. 선택 상태를 check 색만이 아니라 선택 semantics와 text context로 이해할 수 있어야 한다.

좁은 화면과 긴 콘텐츠

Trigger의 선택 값은 한 줄로 제한된다. 긴 후보 이름이 핵심 차이를 가리면 후보 문구를 앞부분에서 구분되게 작성한다. touch 화면에서 너무 많은 후보를 작은 overlay에 넣지 말고 검색이나 별도 선택 화면으로 바꾼다.

공개 API

  • Select: root
  • SelectTrigger, SelectValue: control과 현재 값
  • SelectContent: position="popper"가 기본인 후보 layer
  • SelectGroup, SelectLabel, SelectItem, SelectSeparator: 후보 구조
  • SelectScrollUpButton, SelectScrollDownButton: 긴 목록 scroll control

portal은 Content 내부에서 사용되며 별도 public export가 아니다. 전용 multi-select, searchable, error prop은 없다.

예제

사용 코드 · TSX
import {
  Label,
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '@classum/dot-design-system/ui';

export function ReviewOwnerSelect() {
  return (
    <div className="grid gap-2">
      <Label htmlFor="review-owner">검토 담당자</Label>
      <Select name="reviewOwner">
        <SelectTrigger id="review-owner">
          <SelectValue placeholder="담당자 선택" />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="content-team">콘텐츠 운영팀</SelectItem>
          <SelectItem value="learning-team">학습 설계팀</SelectItem>
        </SelectContent>
      </Select>
    </div>
  );
}

피해야 할 사용

  • placeholder만 두고 Label을 생략하지 않는다.
  • 후보가 두 개뿐인데 자주 전환하는 값을 Select 안에 숨기지 않는다.
  • 검색이 필요한 긴 목록을 그대로 넣지 않는다.
  • 여러 값을 선택하는 UI를 Item click으로 임의 구현하지 않는다.
  • 현재 값이 사라졌는데 아무 설명 없이 첫 후보로 바꾸지 않는다.

관련 항목