Select

사용자가 미리 정의된 옵션 목록 중 하나 혹은 하나 이상의 항목을 선택할 수 있는 입력 요소입니다.

Basic select

Select는 여러 옵션 중 하나의 값을 선택할 수 있습니다.

import { Select, Option, OptionGroup } from '@montage-ui/core';

const Demo = () => {
return (
  <Select width="25ch" placeholder="선택해주세요.">
    {new Array(3).fill(0).map((_, i) => (
      <OptionGroup key={i} title={`그룹 ${i + 1}`}>
        {new Array(3).fill(0)
          .map((__, j) => (
            <Option key={j} value={`값 ${i} ${j}`}>
              {`값 ${i} ${j}`}
            </Option>
          ))
        }
      </OptionGroup>
    ))}
  </Select>
)
}

export default Demo;

Anatomy

아래와 같은 구조로 조합하여 사용합니다.

<Select>
  <OptionGroup>
    <Option value="1">옵션1</Option>
  </OptionGroup>
  <OptionGroup>
    <Option value="2">옵션2</Option>
  </OptionGroup>
</Select>

Multiple select

SelectMultiple 을 사용하여 여러 옵션 중 하나 이상의 값을 선택할 수 있습니다.

import { SelectMultiple, Option, OptionGroup } from '@montage-ui/core';

const Demo = () => {
return (
  <SelectMultiple width="25ch" placeholder="선택해주세요.">
    {new Array(3).fill(0).map((_, i) => (
      <OptionGroup key={i} title={`그룹 ${i + 1}`}>
        {new Array(3).fill(0)
          .map((__, j) => (
            <Option key={j} value={`값 ${i} ${j}`}>
              {`값 ${i} ${j}`}
            </Option>
          ))
        }
      </OptionGroup>
    ))}
  </SelectMultiple>
)
}

export default Demo;

Render

render 옵션을 사용하여 텍스트 대신 SelectRenderChip 으로 렌더링 하거나 커스텀 텍스트를 작성할 수 있습니다.

onClick이 들어가는 경우 e.stopPropagation() 을 통해 메뉴가 열리고 닫히는 동작을 막을 수 있습니다.

import { SelectRenderChip, SelectMultiple, Option, OptionGroup } from '@montage-ui/core';
import { useState, useRef } from 'react';

const Demo = () => {
const [value, setValue] = useState(['값 1 0', '값 2 0', '값 0 2']);
const ref = useRef(null);

return (
  <SelectMultiple
    ref={ref}
    value={value}
    onChange={setValue}
    width="30ch"
    placeholder="선택해주세요."
    render={() => (
      value.map((v, i) => (
        <SelectRenderChip
          key={v}
          onClick={(e) => {
            e.stopPropagation();
            setValue(value.filter(data => data !== v))
            ref.current?.focus();
          }}
          status={i === 1 ? 'negative' : 'normal'}
          disabled={i === 0}
        >
          {v}
        </SelectRenderChip>
      ))
    )}
  >
    {new Array(3).fill(0).map((_, i) => (
      <OptionGroup key={i} title={`그룹 ${i + 1}`}>
        {new Array(3).fill(0)
          .map((__, j) => (
            <Option key={j} value={`값 ${i} ${j}`}>
              {`값 ${i} ${j}`}
            </Option>
          ))
        }
      </OptionGroup>
    ))}
  </SelectMultiple>
)
}

export default Demo;

Overflow

overflow prop 으로 선택된 값이 필드 너비를 넘어갈 때의 동작을 지정합니다.

  • false (default): 필드를 한 줄로 유지합니다. 텍스트는 말줄임, Chip은 가로 스크롤로 나머지 값을 탐색합니다.
  • true: 값이 모두 보이도록 줄바꿈되며 필드의 높이가 늘어납니다.

Select 는 항상 하나의 값만 가지므로 overflow 를 제공하지 않습니다.

Text

import { FlexBox, SelectMultiple, Option, OptionGroup } from '@montage-ui/core';

const OPTIONS = ['프론트엔드 개발', '백엔드 개발', '데이터 엔지니어링', '프로덕트 디자인', '서비스 기획'];

const Demo = () => {
  return (
    <FlexBox gap="12px" flexDirection="column" alignItems="center">
      <SelectMultiple defaultValue={OPTIONS} width="25ch" placeholder="Overflow false">
        <OptionGroup title="그룹 1">
          {OPTIONS.map((option) => (
            <Option variant="checkbox" key={option} value={option}>{option}</Option>
          ))}
        </OptionGroup>
      </SelectMultiple>

      <SelectMultiple overflow defaultValue={OPTIONS} width="25ch" placeholder="Overflow true">
        <OptionGroup title="그룹 1">
          {OPTIONS.map((option) => (
            <Option variant="checkbox" key={option} value={option}>{option}</Option>
          ))}
        </OptionGroup>
      </SelectMultiple>
    </FlexBox>
  )
}

export default Demo;

Chip

import { SelectRenderChip, FlexBox, SelectMultiple, Option, OptionGroup } from '@montage-ui/core';
import { useState, useRef } from 'react';

const OPTIONS = ['프론트엔드 개발', '백엔드 개발', '데이터 엔지니어링', '프로덕트 디자인', '서비스 기획'];

const Demo = () => {
  const [scrollValue, setScrollValue] = useState(OPTIONS);
  const [wrapValue, setWrapValue] = useState(OPTIONS);
  const scrollRef = useRef(null);
  const wrapRef = useRef(null);

  const renderChips = (value, setValue, ref) => () => (
    value.map((v) => (
      <SelectRenderChip
        key={v}
        onClick={(e) => {
          e.stopPropagation();
          setValue(value.filter((data) => data !== v));
          ref.current.focus();
        }}
      >
        {v}
      </SelectRenderChip>
    ))
  );

  return (
    <FlexBox gap="12px" flexDirection="column" alignItems="center">
      <SelectMultiple
        width="25ch"
        placeholder="Overflow false"
        value={scrollValue}
        onChange={setScrollValue}
        ref={scrollRef}
        render={renderChips(scrollValue, setScrollValue, scrollRef)}
      >
        <OptionGroup title="그룹 1">
          {OPTIONS.map((option) => (
            <Option variant="checkbox" key={option} value={option}>{option}</Option>
          ))}
        </OptionGroup>
      </SelectMultiple>

      <SelectMultiple
        overflow
        width="25ch"
        placeholder="Overflow true"
        value={wrapValue}
        onChange={setWrapValue}
        ref={wrapRef}
        render={renderChips(wrapValue, setWrapValue, wrapRef)}
      >
        <OptionGroup title="그룹 1">
          {OPTIONS.map((option) => (
            <Option variant="checkbox" key={option} value={option}>{option}</Option>
          ))}
        </OptionGroup>
      </SelectMultiple>
    </FlexBox>
  )
}

export default Demo;

All selected label

allSelectedLabel prop을 사용하여 모든 옵션이 선택되었을 때 표시할 label을 커스텀할 수 있습니다.

import { SelectMultiple, Option, OptionGroup } from '@montage-ui/core';

const Demo = () => {
return (
  <SelectMultiple defaultValue={['값 0 0', '값 0 1', '값 0 2']} width="25ch" placeholder="선택해주세요." allSelectedLabel="전체">
    {new Array(1).fill(0).map((_, i) => (
      <OptionGroup key={i} title={`그룹 ${i + 1}`}>
        {new Array(3).fill(0)
          .map((__, j) => (
            <Option key={j} value={`값 ${i} ${j}`}>
              {`값 ${i} ${j}`}
            </Option>
          ))
        }
      </OptionGroup>
    ))}
  </SelectMultiple>
)
}

export default Demo;

Variants

Option 컴포넌트에 variant prop을 주어 다양한 형태의 옵션을 렌더링 할 수 있습니다.

  • normal (default)
  • radio (single only)
  • checkbox (multiple only)
import { SelectMultiple, Select, Option, OptionGroup, FlexBox } from '@montage-ui/core';

const Demo = () => {
return (
  <FlexBox gap="12px" flexDirection="column" alignItems="center">
    <Select width="25ch" placeholder="Normal">
      {new Array(3).fill(0).map((_, i) => (
        <OptionGroup key={i} title={`그룹 ${i + 1}`}>
          {new Array(3).fill(0)
            .map((__, j) => (
              <Option variant="normal" key={j} value={`값 ${i} ${j}`}>
                {`값 ${i} ${j}`}
              </Option>
            ))
          }
        </OptionGroup>
      ))}
    </Select>
  
    <Select width="25ch" placeholder="Radio">
      {new Array(3).fill(0).map((_, i) => (
        <OptionGroup key={i} title={`그룹 ${i + 1}`}>
          {new Array(3).fill(0)
            .map((__, j) => (
              <Option variant="radio" key={j} value={`값 ${i} ${j}`}>
                {`값 ${i} ${j}`}
              </Option>
            ))
          }
        </OptionGroup>
      ))}
    </Select>

    <SelectMultiple width="25ch" placeholder="Checkbox">
      {new Array(3).fill(0).map((_, i) => (
        <OptionGroup key={i} title={`그룹 ${i + 1}`}>
          {new Array(3).fill(0)
            .map((__, j) => (
              <Option variant="checkbox" key={j} value={`값 ${i} ${j}`}>
                {`값 ${i} ${j}`}
              </Option>
            ))
          }
        </OptionGroup>
      ))}
    </SelectMultiple>
  </FlexBox>
)
}

export default Demo;

States

validation 을 통해 여러 상태를 표현할 수 있습니다.

  • default
  • negative (status="negative")
  • disabled
import { Select, Option, OptionGroup, FlexBox } from '@montage-ui/core';

const Demo = () => {
return (
  <FlexBox gap="12px" flexDirection="column" alignItems="center">
    <Select width="25ch" placeholder="선택해주세요.">
      {new Array(3).fill(0).map((_, i) => (
        <OptionGroup key={i} title={`그룹 ${i + 1}`}>
          {new Array(3).fill(0)
            .map((__, j) => (
              <Option key={j} value={`값 ${i} ${j}`}>
                {`값 ${i} ${j}`}
              </Option>
            ))
          }
        </OptionGroup>
      ))}
    </Select>

    <Select status="negative" width="25ch" placeholder="선택해주세요.">
      {new Array(3).fill(0).map((_, i) => (
        <OptionGroup key={i} title={`그룹 ${i + 1}`}>
          {new Array(3).fill(0)
            .map((__, j) => (
              <Option key={j} value={`값 ${i} ${j}`}>
                {`값 ${i} ${j}`}
              </Option>
            ))
          }
        </OptionGroup>
      ))}
    </Select>

    <Select disabled width="25ch" placeholder="선택해주세요.">
      {new Array(3).fill(0).map((_, i) => (
        <OptionGroup key={i} title={`그룹 ${i + 1}`}>
          {new Array(3).fill(0)
            .map((__, j) => (
              <Option key={j} value={`값 ${i} ${j}`}>
                {`값 ${i} ${j}`}
              </Option>
            ))
          }
        </OptionGroup>
      ))}
    </Select>

    <Select disabled width="25ch" placeholder="선택해주세요." value="값 0 0">
      {new Array(3).fill(0).map((_, i) => (
        <OptionGroup key={i} title={`그룹 ${i + 1}`}>
          {new Array(3).fill(0)
            .map((__, j) => (
              <Option key={j} value={`값 ${i} ${j}`}>
                {`값 ${i} ${j}`}
              </Option>
            ))
          }
        </OptionGroup>
      ))}
    </Select>
  </FlexBox>
)
}

export default Demo;

Sizes

2가지 사이즈를 제공합니다.

  • large (default)
  • medium

Helper Message

Helper Message

import { FlexBox, FormControl, FormControlField, FormControlLabel, FormControlMessage, Select, Option, OptionGroup } from '@montage-ui/core';

const Demo = () => {
  return (
    <FlexBox gap="12px" flexDirection="column" alignItems="center">
      <FormControl size="large">
        <FormControlLabel required>Large</FormControlLabel>
        <FormControlField>
          <Select size="large" width="25ch" placeholder="Large">
            <OptionGroup title="그룹 1">
              <Option value="옵션1">옵션1</Option>
              <Option value="옵션2">옵션2</Option>
            </OptionGroup>
          </Select>
        </FormControlField>
        <FormControlMessage>Helper Message</FormControlMessage>
      </FormControl>

      <FormControl size="medium">
        <FormControlLabel required>Medium</FormControlLabel>
        <FormControlField>
          <Select size="medium" width="25ch" placeholder="Medium">
            <OptionGroup title="그룹 1">
              <Option value="옵션1">옵션1</Option>
              <Option value="옵션2">옵션2</Option>
            </OptionGroup>
          </Select>
        </FormControlField>
        <FormControlMessage>Helper Message</FormControlMessage>
      </FormControl>
    </FlexBox>
  )
}

export default Demo;

SelectMultiple 도 동일한 사이즈 체계를 사용합니다.

Helper Message

Helper Message

Helper Message

Helper Message

import { FlexBox, SelectRenderChip, SelectMultiple, Option, OptionGroup, FormControl, FormControlField, FormControlLabel, FormControlMessage } from '@montage-ui/core';
import { useState, useRef } from 'react';

const OPTIONS = ['프론트엔드 개발', '백엔드 개발', '데이터 엔지니어링', '프로덕트 디자인', '서비스 기획'];

const Demo = () => {
  const [mediumValue, setMediumValue] = useState(OPTIONS);
  const [largeValue, setLargeValue] = useState(OPTIONS);
  const mediumRef = useRef(null);
  const largeRef = useRef(null);

  const renderChips = (value, setValue, ref) => () => (
    value.map((v) => (
      <SelectRenderChip
        key={v}
        onClick={(e) => {
          e.stopPropagation();
          setValue(value.filter((data) => data !== v));
          ref.current.focus();
        }}
      >
        {v}
      </SelectRenderChip>
    ))
  );

  return (
    <FlexBox gap="12px" flexDirection="column" alignItems="center">
      <FormControl size="large">
        <FormControlLabel required>Large</FormControlLabel>
        <FormControlField>
          <SelectMultiple size="large" width="25ch" placeholder="Large">
            <OptionGroup title="그룹 1">
              <Option value="옵션1">옵션1</Option>
              <Option value="옵션2">옵션2</Option>
            </OptionGroup>
          </SelectMultiple>
        </FormControlField>
        <FormControlMessage>Helper Message</FormControlMessage>
      </FormControl>

      <FormControl size="medium">
        <FormControlLabel required>Medium</FormControlLabel>
        <FormControlField>
          <SelectMultiple size="medium" width="25ch" placeholder="Medium">
            <OptionGroup title="그룹 1">
              <Option value="옵션1">옵션1</Option>
              <Option value="옵션2">옵션2</Option>
            </OptionGroup>
          </SelectMultiple>
        </FormControlField>
        <FormControlMessage>Helper Message</FormControlMessage>
      </FormControl>

      <FormControl size="large">
        <FormControlLabel required>Large</FormControlLabel>
        <FormControlField>
          <SelectMultiple
            size="large"
            width="25ch"
            placeholder="Large with chip"
            value={largeValue}
            onChange={setLargeValue}
            ref={largeRef}
            render={renderChips(largeValue, setLargeValue, largeRef)}
          >
            <OptionGroup title="그룹 1">
              <Option value="옵션1">옵션1</Option>
              <Option value="옵션2">옵션2</Option>
            </OptionGroup>
          </SelectMultiple>
        </FormControlField>
        <FormControlMessage>Helper Message</FormControlMessage>
      </FormControl>

      <FormControl size="medium">
        <FormControlLabel required>Medium</FormControlLabel>
        <FormControlField>
          <SelectMultiple
            size="medium"
            width="25ch"
            placeholder="Medium with chip"
            value={mediumValue}
            onChange={setMediumValue}
            ref={mediumRef}
            render={renderChips(mediumValue, setMediumValue, mediumRef)}
          >
            <OptionGroup title="그룹 1">
              <Option value="옵션1">옵션1</Option>
              <Option value="옵션2">옵션2</Option>
            </OptionGroup>
          </SelectMultiple>
        </FormControlField>
        <FormControlMessage>Helper Message</FormControlMessage>
      </FormControl>
    </FlexBox>
  )
}

export default Demo;

Contents

With select content

Select, SelectMultiple 컴포넌트에 leadingContent prop 을 사용하여 요소의 앞쪽에 콘텐츠를 추가할 수 있습니다.

해당 옵션을 사용할 때에는 SelectContent 로 감싸서 사용합니다.

icon-button 의 IconButton 은 size 를 지정하지 않으면 Select size(반응형 포함)에 맞는 size 와 interactionOverflow 가 자동으로 적용됩니다. IconButton 에 직접 지정하면 지정한 값이 우선합니다.

또한 IconButton 클릭 시 메뉴가 열리지 않도록 e.stopPropagation() 을 함께 사용합니다.

Large

import { SelectRenderChip, Select, SelectMultiple, Option, OptionGroup, SelectContent, FlexBox, IconButton } from '@montage-ui/core';
import { IconBlank } from '@montage-ui/icon';
import { useState, useRef } from 'react';

const OPTIONS = ['프론트엔드 개발', '백엔드 개발', '데이터 엔지니어링', '프로덕트 디자인', '서비스 기획'];

const Demo = () => {
  const [iconValue, setIconValue] = useState([]);
  const [iconButtonValue, setIconButtonValue] = useState([]);
  const iconRef = useRef(null);
  const iconButtonRef = useRef(null);

  const renderChips = (value, setValue, ref) => () => (
    value.map((v) => (
      <SelectRenderChip
        key={v}
        onClick={(e) => {
          e.stopPropagation();
          setValue(value.filter((data) => data !== v));
          ref.current.focus();
        }}
      >
        {v}
      </SelectRenderChip>
    ))
  );

  return (
    <FlexBox gap="12px" flexDirection="column" alignItems="center">
      <Select
        size="large"
        width="25ch"
        placeholder="Icon"
        leadingContent={(
          <SelectContent variant="icon">
            <IconBlank />
          </SelectContent>
        )}
      >
        <OptionGroup title="그룹 1">
          <Option value="옵션1">옵션1</Option>
          <Option value="옵션2">옵션2</Option>
        </OptionGroup>
      </Select>

      <Select
        size="large"
        width="25ch"
        placeholder="Icon button"
        leadingContent={(
          <SelectContent variant="icon-button">
            <IconButton onClick={(e) => e.stopPropagation()}>
              <IconBlank />
            </IconButton>
          </SelectContent>
        )}
      >
        <OptionGroup title="그룹 1">
          <Option value="옵션1">옵션1</Option>
          <Option value="옵션2">옵션2</Option>
        </OptionGroup>
      </Select>

      <SelectMultiple
        size="large"
        width="25ch"
        placeholder="Multiple, Icon"
        leadingContent={(
          <SelectContent variant="icon">
            <IconBlank />
          </SelectContent>
        )}
      >
        <OptionGroup title="그룹 1">
          <Option variant="checkbox" value="옵션1">옵션1</Option>
          <Option variant="checkbox" value="옵션2">옵션2</Option>
        </OptionGroup>
      </SelectMultiple>

      <SelectMultiple
        size="large"
        width="25ch"
        placeholder="Multiple, Icon button"
        leadingContent={(
          <SelectContent variant="icon-button">
            <IconButton onClick={(e) => e.stopPropagation()}>
              <IconBlank />
            </IconButton>
          </SelectContent>
        )}
      >
        <OptionGroup title="그룹 1">
          <Option variant="checkbox" value="옵션1">옵션1</Option>
          <Option variant="checkbox" value="옵션2">옵션2</Option>
        </OptionGroup>
      </SelectMultiple>

      <SelectMultiple
        size="large"
        width="25ch"
        placeholder="Multiple, Icon, chip"
        ref={iconRef}
        value={iconValue}
        onChange={setIconValue}
        render={renderChips(iconValue, setIconValue, iconRef)}
        leadingContent={(
          <SelectContent variant="icon">
            <IconBlank />
          </SelectContent>
        )}
      >
        <OptionGroup title="그룹 1">
          <Option variant="checkbox" value="옵션1">옵션1</Option>
          <Option variant="checkbox" value="옵션2">옵션2</Option>
        </OptionGroup>
      </SelectMultiple>

      <SelectMultiple
        size="large"
        width="25ch"
        placeholder="Multiple, Icon button, chip"
        ref={iconButtonRef}
        value={iconButtonValue}
        onChange={setIconButtonValue}
        render={renderChips(iconButtonValue, setIconButtonValue, iconButtonRef)}
        leadingContent={(
          <SelectContent variant="icon-button">
            <IconButton onClick={(e) => e.stopPropagation()}>
              <IconBlank />
            </IconButton>
          </SelectContent>
        )}
      >
        <OptionGroup title="그룹 1">
          <Option variant="checkbox" value="옵션1">옵션1</Option>
          <Option variant="checkbox" value="옵션2">옵션2</Option>
        </OptionGroup>
      </SelectMultiple>
    </FlexBox>
  )
}

export default Demo;

Medium

import { SelectRenderChip, Select, SelectMultiple, Option, OptionGroup, SelectContent, FlexBox, IconButton } from '@montage-ui/core';
import { IconBlank } from '@montage-ui/icon';
import { useState, useRef } from 'react';

const OPTIONS = ['프론트엔드 개발', '백엔드 개발', '데이터 엔지니어링', '프로덕트 디자인', '서비스 기획'];

const Demo = () => {
  const [iconValue, setIconValue] = useState([]);
  const [iconButtonValue, setIconButtonValue] = useState([]);
  const iconRef = useRef(null);
  const iconButtonRef = useRef(null);

  const renderChips = (value, setValue, ref) => () => (
    value.map((v) => (
      <SelectRenderChip
        key={v}
        onClick={(e) => {
          e.stopPropagation();
          setValue(value.filter((data) => data !== v));
          ref.current.focus();
        }}
      >
        {v}
      </SelectRenderChip>
    ))
  );

  return (
    <FlexBox gap="12px" flexDirection="column" alignItems="center">
      <Select
        size="medium"
        width="25ch"
        placeholder="Icon"
        leadingContent={(
          <SelectContent variant="icon">
            <IconBlank />
          </SelectContent>
        )}
      >
        <OptionGroup title="그룹 1">
          <Option value="옵션1">옵션1</Option>
          <Option value="옵션2">옵션2</Option>
        </OptionGroup>
      </Select>

      <Select
        size="medium"
        width="25ch"
        placeholder="Icon button"
        leadingContent={(
          <SelectContent variant="icon-button">
            <IconButton onClick={(e) => e.stopPropagation()}>
              <IconBlank />
            </IconButton>
          </SelectContent>
        )}
      >
        <OptionGroup title="그룹 1">
          <Option value="옵션1">옵션1</Option>
          <Option value="옵션2">옵션2</Option>
        </OptionGroup>
      </Select>

      <SelectMultiple
        size="medium"
        width="25ch"
        placeholder="Multiple, Icon"
        leadingContent={(
          <SelectContent variant="icon">
            <IconBlank />
          </SelectContent>
        )}
      >
        <OptionGroup title="그룹 1">
          <Option variant="checkbox" value="옵션1">옵션1</Option>
          <Option variant="checkbox" value="옵션2">옵션2</Option>
        </OptionGroup>
      </SelectMultiple>

      <SelectMultiple
        size="medium"
        width="25ch"
        placeholder="Multiple, Icon button"
        leadingContent={(
          <SelectContent variant="icon-button">
            <IconButton onClick={(e) => e.stopPropagation()}>
              <IconBlank />
            </IconButton>
          </SelectContent>
        )}
      >
        <OptionGroup title="그룹 1">
          <Option variant="checkbox" value="옵션1">옵션1</Option>
          <Option variant="checkbox" value="옵션2">옵션2</Option>
        </OptionGroup>
      </SelectMultiple>

      <SelectMultiple
        size="medium"
        width="25ch"
        placeholder="Multiple, Icon, chip"
        ref={iconRef}
        value={iconValue}
        onChange={setIconValue}
        render={renderChips(iconValue, setIconValue, iconRef)}
        leadingContent={(
          <SelectContent variant="icon">
            <IconBlank />
          </SelectContent>
        )}
      >
        <OptionGroup title="그룹 1">
          <Option variant="checkbox" value="옵션1">옵션1</Option>
          <Option variant="checkbox" value="옵션2">옵션2</Option>
        </OptionGroup>
      </SelectMultiple>

      <SelectMultiple
        size="medium"
        width="25ch"
        placeholder="Multiple, Icon button, chip"
        ref={iconButtonRef}
        value={iconButtonValue}
        onChange={setIconButtonValue}
        render={renderChips(iconButtonValue, setIconButtonValue, iconButtonRef)}
        leadingContent={(
          <SelectContent variant="icon-button">
            <IconButton onClick={(e) => e.stopPropagation()}>
              <IconBlank />
            </IconButton>
          </SelectContent>
        )}
      >
        <OptionGroup title="그룹 1">
          <Option variant="checkbox" value="옵션1">옵션1</Option>
          <Option variant="checkbox" value="옵션2">옵션2</Option>
        </OptionGroup>
      </SelectMultiple>
    </FlexBox>
  )
}

export default Demo;

With option content

Option 컴포넌트에 leadingContent, trailingContent prop 을 사용하여 옵션의 앞/뒤에 콘텐츠를 추가할 수 있습니다.

OptionContent 로 감싸서 사용합니다.

import { Select, Option, OptionContent, FlexBox, IconButton, Avatar, ContentBadge } from '@montage-ui/core';
import { IconBlank } from '@montage-ui/icon';

const Demo = () => {
  return (
    <FlexBox gap="12px" flexDirection="column" alignItems="center">
      <Select width="25ch" placeholder="Option content">
        <Option
          value="1"
          trailingContent={(
            <OptionContent variant="value" chevron>
              값
            </OptionContent>
          )}
        >
          Chevron
        </Option>
        <Option
          value="2"
          alignItems="center"
          leadingContent={(
            <OptionContent variant="avatar">
              <Avatar />
            </OptionContent>
          )}
        >
          Avatar
        </Option>
        <Option
          value="3"
          alignItems="center"
          trailingContent={(
            <OptionContent variant="content-badge">
              <ContentBadge>
                Badge
              </ContentBadge>
            </OptionContent>
          )}
        >
          Badge
        </Option>
      </Select>
    </FlexBox>
  )
}

export default Demo;

With option label trailing

Option 컴포넌트에 labelTrailing prop 을 사용하여 레이블 우측에 콘텐츠를 추가할 수 있습니다.

OptionLabelTrailing 으로 감싸서 사용합니다.

import { Select, Option, OptionLabelTrailing, FlexBox, ContentBadge } from '@montage-ui/core';

const Demo = () => {
  return (
    <FlexBox gap="12px" flexDirection="column" alignItems="center">
      <Select width="25ch" placeholder="Option label trailing">
        <Option
          value="1"
          labelTrailing={(
            <OptionLabelTrailing variant="verified-check" />
          )}
        >
          Verified check
        </Option>
        <Option
          value="2"
          labelTrailing={(
            <OptionLabelTrailing variant="content-badge">
              <ContentBadge color="neutral">Badge</ContentBadge>
            </OptionLabelTrailing>
          )}
        >
          Content badge
        </Option>
      </Select>
    </FlexBox>
  )
}

export default Demo;

With option extra content

Option 컴포넌트에 extraContent prop 을 사용하여 텍스트 영역 하단에 추가 콘텐츠를 추가할 수 있습니다.

OptionExtraContent 로 감싸서 사용합니다.

import { Select, Option, OptionExtraContent, FlexBox, ContentBadge } from '@montage-ui/core';

const Demo = () => {
  return (
    <FlexBox gap="12px" flexDirection="column" alignItems="center">
      <Select width="25ch" placeholder="Option extra content">
        <Option
          value="1"
          extraContent={(
            <OptionExtraContent variant="text">
              Extra text
            </OptionExtraContent>
          )}
        >
          Text
        </Option>
        <Option
          value="2"
          extraContent={(
            <OptionExtraContent variant="content-badge">
              <ContentBadge color="neutral">Badge</ContentBadge>
            </OptionExtraContent>
          )}
        >
          Content badge
        </Option>
      </Select>
    </FlexBox>
  )
}

export default Demo;

With action area

Action Area 영역을 추가할 수 있습니다.

Action Area 영역을 사용한다면 enableMenuActionArea prop 을 주어 value 와 menuValue 두 가지 상태로 관리할 수 있습니다.

  • value: 실제 값
  • menuValue: 메뉴에서 선택된 값
import { Select, Option, OptionGroup, MenuActionArea, MenuActionAreaContent, Button } from '@montage-ui/core';
import { IconRefresh } from '@montage-ui/icon';
import { useState } from 'react';

const Demo = () => {
const [value, setValue] = useState('');
const [menuValue, setMenuValue] = useState('');
const [open, setOpen] = useState(false);

return (
  <Select
    enableMenuActionArea
    open={open}
    onOpenChange={setOpen}
    value={value}
    onChange={setValue}
    menuValue={menuValue}
    onMenuValueChange={setMenuValue}
    width="25ch"
    placeholder="선택해주세요."
  >
    {new Array(3).fill(0).map((_, i) => (
      <OptionGroup key={i} title={`그룹 ${i + 1}`}>
        {new Array(3).fill(0)
          .map((__, j) => (
            <Option key={j} value={`값 ${i} ${j}`}>
              {`값 ${i} ${j}`}
            </Option>
          ))
        }
      </OptionGroup>
    ))}

    <MenuActionArea
      leadingContent={
        <MenuActionAreaContent variant="button">
          <Button
            variant="outlined"
            color="assistive"
            iconOnly
            onClick={() => setMenuValue('')}
          >
            <IconRefresh />
          </Button> 
        </MenuActionAreaContent>
      }
      trailingContent={
        <MenuActionAreaContent variant="button">
          <Button onClick={() => {
            setValue(menuValue);
            setOpen(false);
          }}>
            적용
          </Button>
        </MenuActionAreaContent>
      }
    />
  </Select>
)
}

export default Demo;

Form control

Form과 관련된 컴포넌트와 함께 사용할 수 있습니다.

  • FormControlGroup
  • FormControl
  • FormControlField
  • FormControlLabel
  • FormControlMessage
  • FormControlNegativeMessage
  • FormControlPositiveMessage
  • FormControlMessageAccessory

Helper Message

Error Message

import { FlexBox, FormControl, FormControlField, FormControlLabel, FormControlMessage, FormControlNegativeMessage, Select, Option, OptionGroup } from '@montage-ui/core';

const Demo = () => {
  return (
    <FlexBox gap="12px" flexDirection="column" alignItems="center">
      <FormControl>
        <FormControlLabel required>Label</FormControlLabel>
        <FormControlField>
          <Select width="25ch" placeholder="Form control">
            <OptionGroup title="그룹 1">
              <Option value="옵션1">옵션1</Option>
              <Option value="옵션2">옵션2</Option>
            </OptionGroup>
          </Select>
        </FormControlField>
        <FormControlMessage>Helper Message</FormControlMessage>
      </FormControl>

      <FormControl>
        <FormControlLabel required>Label</FormControlLabel>
        <FormControlField>
          <Select width="25ch" placeholder="Invalid form control" status="negative">
            <OptionGroup title="그룹 1">
              <Option value="옵션1">옵션1</Option>
              <Option value="옵션2">옵션2</Option>
            </OptionGroup>
          </Select>
        </FormControlField>
        <FormControlNegativeMessage>Error Message</FormControlNegativeMessage>
      </FormControl>
    </FlexBox>
  )
}

export default Demo;

Controlled

기본적으로 비제어 컴포넌트로 동작합니다.

value, onChange prop을 사용하면 제어 컴포넌트로 동작합니다.

제어 컴포넌트와 비제어 컴포넌트는 React 공식 문서를 참조해주세요.

import { FlexBox, Select, Option, OptionGroup } from '@montage-ui/core';
import { useState } from 'react';

const Demo = () => {
  const [value, setValue] = useState('');

  return (
    <FlexBox alignItems="center" flexDirection="column" gap="12px">
      <Select
        defaultValue="옵션1"
        width="25ch"
        placeholder="Uncontrolled"
      >
        <OptionGroup title="그룹 1">
          <Option value="옵션1">옵션1</Option>
          <Option value="옵션2">옵션2</Option>
        </OptionGroup>
      </Select>
      <Select
        value={value}
        onChange={setValue}
        width="25ch"
        placeholder="Controlled"
      >
        <OptionGroup title="그룹 1">
          <Option value="옵션1">옵션1</Option>
          <Option value="옵션2">옵션2</Option>
        </OptionGroup>
      </Select>
    </FlexBox>
  )
}

export default Demo;

React Hook Form

react-hook-form과 유연하게 조합해서 사용할 수 있습니다.

옵션을 선택하세요.

import { Button, FlexBox, Select, Option, OptionGroup, FormControl, FormControlField, FormControlLabel, FormControlNegativeMessage, FormControlMessage } from '@montage-ui/core';
import { useForm, Controller } from 'react-hook-form';

const Demo = () => {
  const form = useForm();

  return (
    <FlexBox
      as="form"
      flexDirection="column"
      alignItems="center"
      justifyContent="center"
      gap="20px"
      onSubmit={form.handleSubmit((v) => alert(JSON.stringify(v)))}
    >
      <Controller
        control={form.control}
        name="selection"
        rules={{
          required: {
            value: true,
            message: "필수 값입니다.",
          },
        }}
        render={({ field, formState }) => (
          <FormControl>
            <FormControlLabel required>선택</FormControlLabel>

            <FormControlField>
              <Select
                {...field}
                placeholder="선택하세요."
                width="300px"
                status={Boolean(formState.errors.selection) ? 'negative' : 'normal'}
              >
                <OptionGroup title="그룹 1">
                  <Option value="옵션1">옵션1</Option>
                  <Option value="옵션2">옵션2</Option>
                </OptionGroup>
              </Select>
            </FormControlField>
            
            {!!formState.errors.selection ? (
              <FormControlNegativeMessage>{formState.errors.selection?.message?.toString()}</FormControlNegativeMessage>
            ) : (
              <FormControlMessage>옵션을 선택하세요.</FormControlMessage>
            )}
          </FormControl>
        )}
      />

      <Button type="submit" fullWidth>제출</Button>
    </FlexBox>
  )
}

export default Demo;

Accessibility

WAI-ARIA Combobox Pattern 대부분을 준수합니다.

  • Form Control 와 함께 사용할 때 모든 접근성 속성을 주입할 수 있습니다.
  • 그렇지 않은 경우에는 직접 aria-label 등을 사용해서 접근성을 충족시켜야 합니다.

API

Select

NameTypesdefaultValue
status
"normal" | "negative"
"normal"
disabled
boolean
-
size
"medium" | "large"
-
width
Property.Width<string | number> | undefined
-
height
Property.Height<string | number> | undefined
-
name
string
-
value
string
-
defaultValue
string
""
placeholder
string
-
leadingContent
ReactNode
-
render
(label: ReactNode, value: string) => ReactNode
-
onChange
(value: string) => void
-
contentProps
ComponentProps<typeof MenuContent>
-
open
boolean
-
defaultOpen
boolean
-
onOpenChange
(state: boolean) => void
-
enableMenuActionArea
boolean
false
menuValue
string
-
onMenuValueChange
(value: string) => void
-
children
ReactNode
-
sx
SxProp
-
xl
Merge<Pick<SelectDefaultProps, "size" | "height" | "width">, { sx?: CSSInterpolation; }> | undefined
-
lg
Merge<Pick<SelectDefaultProps, "size" | "height" | "width">, { sx?: CSSInterpolation; }> | undefined
-
md
Merge<Pick<SelectDefaultProps, "size" | "height" | "width">, { sx?: CSSInterpolation; }> | undefined
-
sm
Merge<Pick<SelectDefaultProps, "size" | "height" | "width">, { sx?: CSSInterpolation; }> | undefined
-
xs
Merge<Pick<SelectDefaultProps, "size" | "height" | "width">, { sx?: CSSInterpolation; }> | undefined
-

SelectMultiple

NameTypesdefaultValue
status
"normal" | "negative"
"normal"
disabled
boolean
-
size
"medium" | "large"
-
width
Property.Width<string | number> | undefined
-
height
Property.Height<string | number> | undefined
-
name
string
-
value
string[]
-
defaultValue
string[]
[]
allSelectedLabel
ReactNode
-
leadingContent
ReactNode
-
onChange
(value: string[]) => void
-
placeholder
string
-
render
(label: React.ReactNode[], value: string[]) => ReactNode
-
open
boolean
-
defaultOpen
boolean
-
overflow
boolean
false
onOpenChange
(state: boolean) => void
-
contentProps
ComponentProps<typeof MenuContent>
-
children
ReactNode
-
enableMenuActionArea
boolean
-
menuValue
string[]
-
onMenuValueChange
(value: string[]) => void
-
sx
SxProp
-
xl
Merge<Pick<SelectMultipleDefaultProps, "size" | "height" | "width">, { sx?: CSSInterpolation; }> | undefined
-
lg
Merge<Pick<SelectMultipleDefaultProps, "size" | "height" | "width">, { sx?: CSSInterpolation; }> | undefined
-
md
Merge<Pick<SelectMultipleDefaultProps, "size" | "height" | "width">, { sx?: CSSInterpolation; }> | undefined
-
sm
Merge<Pick<SelectMultipleDefaultProps, "size" | "height" | "width">, { sx?: CSSInterpolation; }> | undefined
-
xs
Merge<Pick<SelectMultipleDefaultProps, "size" | "height" | "width">, { sx?: CSSInterpolation; }> | undefined
-

SelectContent

NameTypesdefaultValue
variant
"icon" | "icon-button" | "custom"
"icon"
children
ReactNode
-
sx
SxProp
-

OptionGroup

NameTypesdefaultValue
title
ReactNode
-
children
ReactNode
-
sx
SxProp
-
xl
Merge<Omit<FlexBoxDefaultProps, "children" | "sx">, { sx?: CSSInterpolation; }> | undefined
-
lg
Merge<Omit<FlexBoxDefaultProps, "children" | "sx">, { sx?: CSSInterpolation; }> | undefined
-
md
Merge<Omit<FlexBoxDefaultProps, "children" | "sx">, { sx?: CSSInterpolation; }> | undefined
-
sm
Merge<Omit<FlexBoxDefaultProps, "children" | "sx">, { sx?: CSSInterpolation; }> | undefined
-
xs
Merge<Omit<FlexBoxDefaultProps, "children" | "sx">, { sx?: CSSInterpolation; }> | undefined
-
flexDirection
Property.FlexDirection | undefined
-
flexWrap
Property.FlexWrap | undefined
-
justifyContent
Property.JustifyContent | undefined
-
alignItems
Property.AlignItems | undefined
-
alignContent
Property.AlignContent | undefined
-
order
Property.Order | undefined
-
flex
Property.Flex<string | number> | undefined
-
flexGrow
Property.FlexGrow | undefined
-
flexShrink
Property.FlexShrink | undefined
-
flexBasis
Property.FlexBasis<string | number> | undefined
-
alignSelf
Property.AlignSelf | undefined
-
gap
Property.Gap<string | number> | undefined
-
rowGap
Property.RowGap<string | number> | undefined
-
columnGap
Property.ColumnGap<string | number> | undefined
-

Option

OptionContent

NameTypesdefaultValue
variant
"button" | "switch" | "icon" | "radio" | "checkbox" | "icon-button" | "toggle-icon" | "text-button" | "content-badge" | "avatar" | "large-icon" | "value" | "thumbnail" | "custom"
-
chevron
boolean
-
children
ReactNode
-
sx
SxProp
-

OptionLabelTrailing

ListCellLabelTrailing과 동일한 props를 사용합니다.

NameTypesdefaultValue
variant
"content-badge" | "custom" | "verified-check"
-
sx
SxProp
-

OptionExtraContent

ListCellExtraContent와 동일한 props를 사용합니다.

NameTypesdefaultValue
variant
"text" | "content-badge" | "custom"
-
sx
SxProp
-

© 2026 Wanted Lab, Inc.