Select

Select

버튼으로 트리거되는, 사용자가 고를 수 있는 옵션 목록을 표시하는 컴포넌트예요.

출처: 문서

본문

버튼을 누르면 선택지를 나열한 메뉴가 펼쳐지고, 그중 하나를 골라 값을 설정하는 드롭다운 셀렉트예요.

<Select.Root defaultValue="apple">
  <Select.Trigger />

  <Select.Content>
    <Select.Group>
      <Select.Label>Fruits</Select.Label>

      <Select.Item value="orange">Orange</Select.Item>

      <Select.Item value="apple">Apple</Select.Item>

      <Select.Item value="grape" disabled>
        Grape
      </Select.Item>
    </Select.Group>

    <Select.Separator />

    <Select.Group>
      <Select.Label>Vegetables</Select.Label>

      <Select.Item value="carrot">Carrot</Select.Item>

      <Select.Item value="potato">Potato</Select.Item>
    </Select.Group>
  </Select.Content>
</Select.Root>

Root

Select의 모든 부분을 담아요. Select primitive의 Root 파트에서 props를 상속받아요.

Prop Type Default
size Responsive<"1" | "2" | "3"> "2"

Trigger

Select를 열고 닫는 버튼이에요. 이 컴포넌트는 Select primitive의 Trigger와 Value 파트에서 props를 상속받아요. 공통 margin props도 지원해요.

Prop Type Default
variant "classic" | "surface" | "soft" | "ghost" "surface"
color enum No default value
radius "none" | "small" | "medium" | "large" | "full" No default value
placeholder string No default value

Content

Select가 열렸을 때 튀어나오는 컴포넌트예요. Select.Portal primitive와 Select.Content primitive 파트에서 props를 상속받아요.

Prop Type Default
variant "solid" | "soft" "solid"
color enum No default value
highContrast boolean No default value

Item

Select의 항목을 담는 컴포넌트예요. Select.Item primitive 파트에서 props를 상속받아요.

Group

여러 항목을 묶는 데 사용해요. Select.Group primitive 파트에서 props를 상속받아요. Select.Label과 함께 사용하면 자동 라벨링으로 좋은 접근성을 보장할 수 있어요.

Label

그룹의 라벨을 렌더링하는 데 사용하며, 화살표 키로 포커스되지 않아요. Select.Label primitive 파트에서 props를 상속받아요.

Separator

Select 안의 항목을 시각적으로 구분하는 데 사용해요. Select.Separator primitive 파트에서 props를 상속받아요.

예시 (Examples)

Size

size prop으로 크기를 제어할 수 있어요.

<Flex gap="3" align="center">
  <Select.Root size="1" defaultValue="apple">
    <Select.Trigger />

    <Select.Content>
      <Select.Item value="apple">Apple</Select.Item>

      <Select.Item value="orange">Orange</Select.Item>
    </Select.Content>
  </Select.Root>

  <Select.Root size="2" defaultValue="apple">
    <Select.Trigger />

    <Select.Content>
      <Select.Item value="apple">Apple</Select.Item>

      <Select.Item value="orange">Orange</Select.Item>
    </Select.Content>
  </Select.Root>

  <Select.Root size="3" defaultValue="apple">
    <Select.Trigger />

    <Select.Content>
      <Select.Item value="apple">Apple</Select.Item>

      <Select.Item value="orange">Orange</Select.Item>
    </Select.Content>
  </Select.Root>
</Flex>

Variant

Trigger와 Content의 variant prop으로 시각적 스타일을 커스터마이징할 수 있어요.

<Flex gap="3" align="center">
  <Select.Root defaultValue="apple">
    <Select.Trigger variant="surface" />

    <Select.Content>
      <Select.Item value="apple">Apple</Select.Item>

      <Select.Item value="orange">Orange</Select.Item>
    </Select.Content>
  </Select.Root>

  <Select.Root defaultValue="apple">
    <Select.Trigger variant="classic" />

    <Select.Content>
      <Select.Item value="apple">Apple</Select.Item>

      <Select.Item value="orange">Orange</Select.Item>
    </Select.Content>
  </Select.Root>

  <Select.Root defaultValue="apple">
    <Select.Trigger variant="soft" />

    <Select.Content>
      <Select.Item value="apple">Apple</Select.Item>

      <Select.Item value="orange">Orange</Select.Item>
    </Select.Content>
  </Select.Root>
</Flex>

Ghost

ghost 트리거 variant를 사용하면 시각적으로 감싸는 요소 없이 트리거를 렌더링할 수 있어요. Ghost 트리거는 레이아웃에서 다르게 동작하는데, 옆 요소들과 시각적으로 정렬되도록 음수 마진을 사용하면서도 패딩이 있는 활성/호버 상태는 유지해요.

<Flex gap="3" align="center">
  <Select.Root defaultValue="apple">
    <Select.Trigger variant="surface" />

    <Select.Content>
      <Select.Item value="apple">Apple</Select.Item>

      <Select.Item value="orange">Orange</Select.Item>
    </Select.Content>
  </Select.Root>

  <Select.Root defaultValue="apple">
    <Select.Trigger variant="ghost" />

    <Select.Content>
      <Select.Item value="apple">Apple</Select.Item>

      <Select.Item value="orange">Orange</Select.Item>
    </Select.Content>
  </Select.Root>
</Flex>

Color

Trigger와 Content의 color prop으로 특정 색상 값을 지정할 수 있어요.

<Flex gap="3">
  <Select.Root defaultValue="apple">
    <Select.Trigger color="indigo" variant="soft" />

    <Select.Content color="indigo">
      <Select.Item value="apple">Apple</Select.Item>

      <Select.Item value="orange">Orange</Select.Item>
    </Select.Content>
  </Select.Root>

  <Select.Root defaultValue="apple">
    <Select.Trigger color="cyan" variant="soft" />

    <Select.Content color="cyan">
      <Select.Item value="apple">Apple</Select.Item>

      <Select.Item value="orange">Orange</Select.Item>
    </Select.Content>
  </Select.Root>

  <Select.Root defaultValue="apple">
    <Select.Trigger color="orange" variant="soft" />

    <Select.Content color="orange">
      <Select.Item value="apple">Apple</Select.Item>

      <Select.Item value="orange">Orange</Select.Item>
    </Select.Content>
  </Select.Root>

  <Select.Root defaultValue="apple">
    <Select.Trigger color="crimson" variant="soft" />

    <Select.Content color="crimson">
      <Select.Item value="apple">Apple</Select.Item>

      <Select.Item value="orange">Orange</Select.Item>
    </Select.Content>
  </Select.Root>
</Flex>

High-contrast

Content의 highContrast prop으로 항목 대비를 높일 수 있어요.

<Flex gap="3">
  <Select.Root defaultValue="apple">
    <Select.Trigger color="gray" />

    <Select.Content color="gray" variant="solid">
      <Select.Item value="apple">Apple</Select.Item>

      <Select.Item value="orange">Orange</Select.Item>
    </Select.Content>
  </Select.Root>

  <Select.Root defaultValue="apple">
    <Select.Trigger color="gray" />

    <Select.Content color="gray" variant="solid" highContrast>
      <Select.Item value="apple">Apple</Select.Item>

      <Select.Item value="orange">Orange</Select.Item>
    </Select.Content>
  </Select.Root>
</Flex>

Radius

radius prop으로 특정 둥근 모서리 값을 지정할 수 있어요.

<Flex gap="3">
  <Select.Root defaultValue="apple">
    <Select.Trigger radius="none" />

    <Select.Content>
      <Select.Item value="apple">Apple</Select.Item>

      <Select.Item value="orange">Orange</Select.Item>
    </Select.Content>
  </Select.Root>

  <Select.Root defaultValue="apple">
    <Select.Trigger radius="large" />

    <Select.Content>
      <Select.Item value="apple">Apple</Select.Item>

      <Select.Item value="orange">Orange</Select.Item>
    </Select.Content>
  </Select.Root>

  <Select.Root defaultValue="apple">
    <Select.Trigger radius="full" />

    <Select.Content>
      <Select.Item value="apple">Apple</Select.Item>

      <Select.Item value="orange">Orange</Select.Item>
    </Select.Content>
  </Select.Root>
</Flex>

Placeholder

placeholder prop으로 초기 값이 필요 없는 Trigger를 만들 수 있어요.

<Select.Root>
  <Select.Trigger placeholder="Pick a fruit" />

  <Select.Content>
    <Select.Group>
      <Select.Label>Fruits</Select.Label>

      <Select.Item value="orange">Orange</Select.Item>

      <Select.Item value="apple">Apple</Select.Item>

      <Select.Item value="grape" disabled>
        Grape
      </Select.Item>
    </Select.Group>

    <Select.Separator />

    <Select.Group>
      <Select.Label>Vegetables</Select.Label>

      <Select.Item value="carrot">Carrot</Select.Item>

      <Select.Item value="potato">Potato</Select.Item>
    </Select.Group>
  </Select.Content>
</Select.Root>

Position

position="popper" prop을 설정하면 셀렉트 메뉴를 트리거 아래에 배치할 수 있어요.

<Select.Root defaultValue="apple">
  <Select.Trigger />

  <Select.Content position="popper">
    <Select.Item value="apple">Apple</Select.Item>

    <Select.Item value="orange">Orange</Select.Item>
  </Select.Content>
</Select.Root>

With SSR

서버 사이드 렌더링을 사용할 때, 하이드레이션 후 레이아웃이 흔들리는(layout shift) 현상이 생길 수 있어요. Trigger가 선택된 항목의 텍스트를 표시하기 위해 클라이언트 코드를 실행하기 때문이에요. 이 레이아웃 변화를 피하려면 값을 매핑해 직접 렌더링할 수 있어요.

() => {
  const data = {
    apple: "Apple",

    orange: "Orange",
  };

  const [value, setValue] = React.useState("apple");

  return (
    <Select.Root value={value} onValueChange={setValue}>
      <Select.Trigger>{data[value]}</Select.Trigger>

      <Select.Content>
        <Select.Item value="apple">Apple</Select.Item>

        <Select.Item value="orange">Orange</Select.Item>
      </Select.Content>
    </Select.Root>
  );
};

With an icon

Trigger가 값을 렌더링하는 방식을 자식 요소를 직접 제어해서 커스터마이징할 수 있어요. 예를 들어 선택된 항목의 텍스트 옆에 아이콘을 렌더링할 수 있어요.

() => {
  const data = {
    light: { label: "Light", icon: <SunIcon /> },

    dark: { label: "Dark", icon: <MoonIcon /> },
  };

  const [value, setValue] = React.useState("light");

  return (
    <Flex direction="column" maxWidth="160px">
      <Select.Root value={value} onValueChange={setValue}>
        <Select.Trigger>
          <Flex as="span" align="center" gap="2">
            {data[value].icon}

            {data[value].label}
          </Flex>
        </Select.Trigger>

        <Select.Content position="popper">
          <Select.Item value="light">Light</Select.Item>

          <Select.Item value="dark">Dark</Select.Item>
        </Select.Content>
      </Select.Root>
    </Flex>
  );
};

더 알아보기 (Learn more)

  • SSR 환경에서는 레이아웃 변화를 피하려고 자식 요소를 직접 매핑해 렌더링하는 방법도 있어요.