Select Box

명확한 테두리를 가진 컨테이너를 활용하여, 정의된 목록 중 하나 이상의 옵션을 선택하는 UI 요소입니다.

import { HStack } from "@seed-design/react";
import {
  CheckSelectBox,
  CheckSelectBoxCheckmark,
  CheckSelectBoxGroup,
  RadioSelectBoxItem,
  RadioSelectBoxRadiomark,
  RadioSelectBoxRoot,
} from "seed-design/ui/select-box";

export default function SelectBoxPreview() {
  return (
    <HStack gap="x6" align="flex-start">
      <CheckSelectBoxGroup aria-label="Fruit">
        <CheckSelectBox label="Apple" defaultChecked suffix={<CheckSelectBoxCheckmark />} />
        <CheckSelectBox
          label="Melon"
          description="Elit cupidatat dolore fugiat enim veniam culpa."
          suffix={<CheckSelectBoxCheckmark />}
        />
        <CheckSelectBox label="Mango" suffix={<CheckSelectBoxCheckmark />} />
      </CheckSelectBoxGroup>

      <RadioSelectBoxRoot defaultValue="apple" aria-label="Fruit">
        <RadioSelectBoxItem value="apple" label="Apple" suffix={<RadioSelectBoxRadiomark />} />
        <RadioSelectBoxItem
          value="melon"
          label="Melon"
          description="Elit cupidatat dolore fugiat enim veniam culpa."
          suffix={<RadioSelectBoxRadiomark />}
        />
        <RadioSelectBoxItem value="mango" label="Mango" suffix={<RadioSelectBoxRadiomark />} />
      </RadioSelectBoxRoot>
    </HStack>
  );
}

Installation

npx @seed-design/cli@latest add ui:select-box

Props

Check Select Box

CheckSelectBoxGroup

Prop

Type

CheckSelectBox

Prop

Type

CheckSelectBoxCheckmark

Prop

Type

Radio Select Box

RadioSelectBoxRoot

Prop

Type

RadioSelectBoxItem

Prop

Type

RadioSelectBoxRadiomark

Prop

Type

Examples

React Hook Form

import { HStack, VStack } from "@seed-design/react";
import { useCallback, type FormEvent } from "react";
import { useController, useForm, type Control } from "react-hook-form";
import { ActionButton } from "seed-design/ui/action-button";
import {
  CheckSelectBox,
  CheckSelectBoxCheckmark,
  CheckSelectBoxGroup,
  RadioSelectBoxItem,
  RadioSelectBoxRadiomark,
  RadioSelectBoxRoot,
} from "seed-design/ui/select-box";

const POSSIBLE_FRUIT_VALUES = ["apple", "melon", "mango"] as const;

type CheckFormValues = Record<(typeof POSSIBLE_FRUIT_VALUES)[number], boolean>;

interface RadioFormValues {
  fruit: (typeof POSSIBLE_FRUIT_VALUES)[number];
}

export default function SelectBoxReactHookForm() {
  // CheckSelectBox Form
  const checkForm = useForm<CheckFormValues>({
    defaultValues: { apple: false, melon: true, mango: false },
  });

  // RadioSelectBox Form
  const radioForm = useForm<RadioFormValues>({
    defaultValues: { fruit: "melon" },
  });
  const { field: radioField } = useController({ name: "fruit", control: radioForm.control });

  const onCheckValid = useCallback((data: CheckFormValues) => {
    window.alert(`CheckSelectBox:\n${JSON.stringify(data, null, 2)}`);
  }, []);

  const onRadioValid = useCallback((data: RadioFormValues) => {
    window.alert(`RadioSelectBox:\n${JSON.stringify(data, null, 2)}`);
  }, []);

  const onCheckReset = useCallback(
    (event: FormEvent) => {
      event.preventDefault();
      checkForm.reset();
    },
    [checkForm],
  );

  const onRadioReset = useCallback(
    (event: FormEvent) => {
      event.preventDefault();
      radioForm.reset();
    },
    [radioForm],
  );

  return (
    <HStack gap="x8" p="x4" width="full" align="flex-start">
      <VStack
        grow
        gap="x3"
        as="form"
        onSubmit={checkForm.handleSubmit(onCheckValid)}
        onReset={onCheckReset}
      >
        <CheckSelectBoxGroup aria-label="Fruit">
          {POSSIBLE_FRUIT_VALUES.map((name) => (
            <CheckSelectBoxItem key={name} name={name} control={checkForm.control} />
          ))}
        </CheckSelectBoxGroup>
        <HStack gap="x2">
          <ActionButton type="reset" variant="neutralWeak">
            초기화
          </ActionButton>
          <ActionButton type="submit" variant="neutralSolid" flexGrow={1}>
            제출
          </ActionButton>
        </HStack>
      </VStack>

      <VStack
        grow
        gap="x3"
        as="form"
        onSubmit={radioForm.handleSubmit(onRadioValid)}
        onReset={onRadioReset}
      >
        <RadioSelectBoxRoot aria-label="Fruit" {...radioField}>
          {POSSIBLE_FRUIT_VALUES.map((value) => (
            <RadioSelectBoxItem
              key={value}
              value={value}
              label={value}
              suffix={<RadioSelectBoxRadiomark />}
            />
          ))}
        </RadioSelectBoxRoot>
        <HStack gap="x2">
          <ActionButton type="reset" variant="neutralWeak">
            초기화
          </ActionButton>
          <ActionButton type="submit" variant="neutralSolid" flexGrow={1}>
            제출
          </ActionButton>
        </HStack>
      </VStack>
    </HStack>
  );
}

interface CheckSelectBoxItemProps {
  name: keyof CheckFormValues;
  control: Control<CheckFormValues>;
}

function CheckSelectBoxItem({ name, control }: CheckSelectBoxItemProps) {
  const {
    field: { value, ...restProps },
    fieldState: { invalid },
  } = useController({ name, control });

  return (
    <CheckSelectBox
      key={name}
      label={name}
      checked={value}
      inputProps={restProps}
      invalid={invalid}
      suffix={<CheckSelectBoxCheckmark />}
    />
  );
}

Customizing Label

import { Badge, HStack } from "@seed-design/react";
import {
  CheckSelectBox,
  CheckSelectBoxCheckmark,
  CheckSelectBoxGroup,
  RadioSelectBoxItem,
  RadioSelectBoxRadiomark,
  RadioSelectBoxRoot,
} from "seed-design/ui/select-box";

export default function SelectBoxCustomizingLabel() {
  return (
    <HStack gap="x8" align="flex-start" p="x4">
      <CheckSelectBoxGroup aria-label="Fruit">
        <CheckSelectBox label="Apple" defaultChecked suffix={<CheckSelectBoxCheckmark />} />
        <CheckSelectBox
          label={
            <>
              Melon
              <Badge tone="brand" variant="solid">
                New
              </Badge>
            </>
          }
          description="Elit cupidatat dolore fugiat enim veniam culpa."
          suffix={<CheckSelectBoxCheckmark />}
        />
        <CheckSelectBox
          label="Mango"
          description="Aliqua ad aute eiusmod eiusmod nulla adipisicing proident ullamco in."
          suffix={<CheckSelectBoxCheckmark />}
        />
      </CheckSelectBoxGroup>

      <RadioSelectBoxRoot defaultValue="apple" aria-label="Fruit">
        <RadioSelectBoxItem value="apple" label="Apple" suffix={<RadioSelectBoxRadiomark />} />
        <RadioSelectBoxItem
          value="melon"
          label={
            <>
              Melon
              <Badge tone="brand" variant="solid">
                New
              </Badge>
            </>
          }
          description="Elit cupidatat dolore fugiat enim veniam culpa."
          suffix={<RadioSelectBoxRadiomark />}
        />
        <RadioSelectBoxItem
          value="mango"
          label="Mango"
          description="Aliqua ad aute eiusmod eiusmod nulla adipisicing proident ullamco in."
          suffix={<RadioSelectBoxRadiomark />}
        />
      </RadioSelectBoxRoot>
    </HStack>
  );
}

Listening to Value Changes

CheckSelectBoxonCheckedChange를 사용하여 체크박스의 선택 상태 변경을 감지할 수 있습니다.

RadioSelectBoxRootonValueChange를 사용하여 라디오 버튼의 선택 값 변경을 감지할 수 있습니다.

import { HStack, Text, VStack } from "@seed-design/react";
import {
  CheckSelectBox,
  CheckSelectBoxCheckmark,
  CheckSelectBoxGroup,
  RadioSelectBoxItem,
  RadioSelectBoxRadiomark,
  RadioSelectBoxRoot,
} from "seed-design/ui/select-box";
import { useState } from "react";

export default function SelectBoxValueChanges() {
  const [checkCount, setCheckCount] = useState(0);
  const [checkLastValue, setCheckLastValue] = useState<boolean | null>(null);

  const [radioCount, setRadioCount] = useState(0);
  const [radioLastValue, setRadioLastValue] = useState<string | null>(null);

  return (
    <HStack gap="x8" align="center" width="full" p="x4">
      <VStack gap="x4" align="center" style={{ flex: 1 }}>
        <CheckSelectBoxGroup aria-label="Fruit">
          <CheckSelectBox
            label="Apple"
            suffix={<CheckSelectBoxCheckmark />}
            onCheckedChange={(checked) => {
              setCheckCount((prev) => prev + 1);
              setCheckLastValue(checked);
            }}
          />
        </CheckSelectBoxGroup>
        <Text align="center">
          onCheckedChange called: {checkCount} times, last value: {`${checkLastValue ?? "-"}`}
        </Text>
      </VStack>

      <VStack gap="x4" align="center" style={{ flex: 1 }}>
        <RadioSelectBoxRoot
          defaultValue="apple"
          aria-label="Fruit"
          onValueChange={(value) => {
            setRadioCount((prev) => prev + 1);
            setRadioLastValue(value);
          }}
        >
          <RadioSelectBoxItem value="apple" label="Apple" suffix={<RadioSelectBoxRadiomark />} />
          <RadioSelectBoxItem value="banana" label="Banana" suffix={<RadioSelectBoxRadiomark />} />
        </RadioSelectBoxRoot>
        <Text align="center">
          onValueChange called: {radioCount} times, last value: {radioLastValue ?? "-"}
        </Text>
      </VStack>
    </HStack>
  );
}

Grid Layout (Columns)

columns prop을 사용하여 여러 열로 배치할 수 있습니다. columns가 1보다 크면 하위 요소의 layout이 자동으로 "vertical"로 설정됩니다.

필요한 경우 layout prop을 직접 설정하여 개별 항목의 레이아웃을 오버라이드할 수 있습니다.

import { IconDiamond, IconIcecreamcone } from "@karrotmarket/react-multicolor-icon";
import { VStack } from "@seed-design/react";
import {
  CheckSelectBox,
  CheckSelectBoxCheckmark,
  CheckSelectBoxGroup,
  RadioSelectBoxItem,
  RadioSelectBoxRadiomark,
  RadioSelectBoxRoot,
} from "seed-design/ui/select-box";

export default function SelectBoxColumns() {
  return (
    <VStack gap="x8" p="x4">
      <CheckSelectBoxGroup columns={2} aria-label="Grid 레이아웃 예제">
        <CheckSelectBox
          prefixIcon={<IconIcecreamcone />}
          label="옵션 1"
          description="layout=vertical"
          suffix={<CheckSelectBoxCheckmark />}
        />
        <CheckSelectBox
          prefixIcon={<IconIcecreamcone />}
          label="옵션 2"
          description="layout=vertical"
          suffix={<CheckSelectBoxCheckmark />}
        />
        <CheckSelectBox
          prefixIcon={<IconIcecreamcone />}
          defaultChecked
          layout="horizontal"
          label="layout=horizontal"
          description="layout을 horizontal로 오버라이드"
          suffix={<CheckSelectBoxCheckmark />}
        />
        <CheckSelectBox
          prefixIcon={<IconIcecreamcone />}
          label="옵션 4"
          description="layout=vertical"
          suffix={<CheckSelectBoxCheckmark />}
        />
      </CheckSelectBoxGroup>

      <RadioSelectBoxRoot columns={3} defaultValue="option3" aria-label="Grid 레이아웃 예제">
        <RadioSelectBoxItem
          value="option1"
          prefixIcon={<IconDiamond />}
          label="옵션 1"
          suffix={<RadioSelectBoxRadiomark />}
        />
        <RadioSelectBoxItem
          value="option2"
          prefixIcon={<IconDiamond />}
          label="옵션 2"
          suffix={<RadioSelectBoxRadiomark />}
        />
        <RadioSelectBoxItem
          value="option3"
          prefixIcon={<IconDiamond />}
          label="layout=horizontal"
          description="layout을 horizontal로 오버라이드"
          layout="horizontal"
          suffix={<RadioSelectBoxRadiomark />}
        />
        <RadioSelectBoxItem
          value="option4"
          prefixIcon={<IconDiamond />}
          label="옵션 4"
          suffix={<RadioSelectBoxRadiomark />}
        />
        <RadioSelectBoxItem
          value="option5"
          prefixIcon={<IconDiamond />}
          label="옵션 5"
          suffix={<RadioSelectBoxRadiomark />}
        />
        <RadioSelectBoxItem
          value="option6"
          prefixIcon={<IconDiamond />}
          label="옵션 6"
          suffix={<RadioSelectBoxRadiomark />}
        />
      </RadioSelectBoxRoot>
    </VStack>
  );
}

With Suffix

suffix prop을 사용하여 체크마크, 라디오 마크, 또는 커스텀 요소를 표시할 수 있습니다. CheckSelectBoxCheckmarkRadioSelectBoxRadiomark를 사용하거나, 아이콘이나 텍스트 등 자유로운 요소를 전달할 수 있습니다.

import { IconPersonCircleLine } from "@karrotmarket/react-monochrome-icon";
import { Text, HStack, Box } from "@seed-design/react";
import {
  CheckSelectBox,
  CheckSelectBoxCheckmark,
  CheckSelectBoxGroup,
  RadioSelectBoxItem,
  RadioSelectBoxRadiomark,
  RadioSelectBoxRoot,
} from "seed-design/ui/select-box";

export default function SelectBoxWithSuffix() {
  return (
    <HStack gap="x8">
      <CheckSelectBoxGroup aria-label="Suffix 예제">
        <CheckSelectBox label="체크마크" suffix={<CheckSelectBoxCheckmark />} />
        <CheckSelectBox
          label="텍스트 suffix"
          suffix={
            <Box flexShrink={0}>
              <Text textStyle="t4Medium" color="fg.neutral">
                +1,000원
              </Text>
            </Box>
          }
        />
        <CheckSelectBox
          label="suffix 없음"
          description="Commodo aliquip fugiat aute irure."
          prefixIcon={<IconPersonCircleLine />}
        />
      </CheckSelectBoxGroup>
      <RadioSelectBoxRoot defaultValue="radiomark" aria-label="RadioMark 예제">
        <RadioSelectBoxItem
          value="radiomark"
          label="라디오 마크"
          suffix={<RadioSelectBoxRadiomark />}
        />
        <RadioSelectBoxItem
          value="text"
          label="텍스트 suffix"
          description="Commodo aliquip fugiat aute irure."
          suffix={
            <Box flexShrink={0}>
              <Text textStyle="t4Medium" color="fg.neutral">
                +1,000원
              </Text>
            </Box>
          }
        />
        <RadioSelectBoxItem
          value="none"
          label="suffix 없음"
          suffix={undefined}
          prefixIcon={<IconPersonCircleLine />}
        />
      </RadioSelectBoxRoot>
    </HStack>
  );
}

footer prop으로 추가 콘텐츠를 표시할 수 있습니다. footerVisibility prop으로 footer의 표시 조건을 제어할 수 있습니다.

  • "when-selected" (기본값): 항목이 선택되었을 때만 표시
  • "when-not-selected": 항목이 선택되지 않았을 때만 표시
  • "always": 항상 표시
import { Box, HStack, Text } from "@seed-design/react";
import {
  CheckSelectBox,
  CheckSelectBoxCheckmark,
  CheckSelectBoxGroup,
  RadioSelectBoxItem,
  RadioSelectBoxRadiomark,
  RadioSelectBoxRoot,
} from "seed-design/ui/select-box";

export default function SelectBoxCollapsibleFooter() {
  return (
    <HStack gap="x8" p="x4" align="flex-start" height="400px">
      <CheckSelectBoxGroup aria-label="Footer 예제">
        <CheckSelectBox
          label="선택 시에만 표시 (기본값)"
          description="footerVisibility='when-selected'"
          suffix={<CheckSelectBoxCheckmark />}
          footer={
            <Box px="x5" pb="x5">
              <Text textStyle="t3Medium">선택되었을 때만 보입니다.</Text>
            </Box>
          }
        />
        <CheckSelectBox
          label="항상 표시"
          description="footerVisibility='always'"
          suffix={<CheckSelectBoxCheckmark />}
          footerVisibility="always"
          footer={
            <Box px="x5" pb="x5">
              <Text textStyle="t3Medium">항상 보입니다.</Text>
            </Box>
          }
        />
        <CheckSelectBox
          label="미선택 시에만 표시"
          description="footerVisibility='when-not-selected'"
          suffix={<CheckSelectBoxCheckmark />}
          footerVisibility="when-not-selected"
          footer={
            <Box px="x5" pb="x5">
              <Text textStyle="t3Medium">선택되지 않았을 때만 보입니다.</Text>
            </Box>
          }
        />
      </CheckSelectBoxGroup>

      <RadioSelectBoxRoot defaultValue="when-selected" aria-label="Footer 예제">
        <RadioSelectBoxItem
          value="when-selected"
          label="선택 시에만 표시 (기본값)"
          description="footerVisibility='when-selected'"
          suffix={<RadioSelectBoxRadiomark />}
          footer={
            <Box px="x5" pb="x5">
              <Text textStyle="t3Medium">선택되었을 때만 보입니다.</Text>
            </Box>
          }
        />
        <RadioSelectBoxItem
          value="always"
          label="항상 표시"
          description="footerVisibility='always'"
          suffix={<RadioSelectBoxRadiomark />}
          footerVisibility="always"
          footer={
            <Box px="x5" pb="x5">
              <Text textStyle="t3Medium">항상 보입니다.</Text>
            </Box>
          }
        />
        <RadioSelectBoxItem
          value="when-not-selected"
          label="미선택 시에만 표시"
          description="footerVisibility='when-not-selected'"
          suffix={<RadioSelectBoxRadiomark />}
          footerVisibility="when-not-selected"
          footer={
            <Box px="x5" pb="x5">
              <Text textStyle="t3Medium">선택되지 않았을 때만 보입니다.</Text>
            </Box>
          }
        />
      </RadioSelectBoxRoot>
    </HStack>
  );
}

Fieldset/RadioGroupField Integration

Fieldset/RadioGroupField 관련 prop을 사용할 수 있습니다.

  • labellabelWeight
  • indicatorshowRequiredIndicator
  • descriptionerrorMessage
  • disabled, invalid, name, form: RadioSelectBoxRoot에만 지원됩니다.
import { ActionButton, HStack, VStack, Box, Text } from "@seed-design/react";
import { useState } from "react";
import {
  CheckSelectBox,
  CheckSelectBoxCheckmark,
  CheckSelectBoxGroup,
  RadioSelectBoxItem,
  RadioSelectBoxRadiomark,
  RadioSelectBoxRoot,
} from "seed-design/ui/select-box";

export default function SelectBoxFieldset() {
  const [checkErrors, setCheckErrors] = useState<Record<string, string | undefined>>({});
  const [radioErrorMessage, setRadioErrorMessage] = useState<string | undefined>();

  const handleCheckSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    const formData = new FormData(e.currentTarget);
    const fruits = formData.getAll("fruit");

    if (fruits.includes("apple")) {
      setCheckErrors({ apple: "Apple은 선택할 수 없습니다." });

      return;
    }

    setCheckErrors({});

    alert(JSON.stringify(fruits, null, 2));
  };

  const handleRadioSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    const formData = new FormData(e.currentTarget);
    const color = formData.get("color");

    if (color === "red") {
      setRadioErrorMessage("Red는 선택할 수 없습니다.");

      return;
    }

    setRadioErrorMessage(undefined);

    alert(JSON.stringify({ color }, null, 2));
  };

  return (
    <HStack width="full" gap="x8" p="x4" align="flex-start" height="400px">
      <VStack asChild gap="spacingY.componentDefault" style={{ flex: 1 }}>
        <form onSubmit={handleCheckSubmit}>
          <CheckSelectBoxGroup
            label="선호하는 과일을 선택하세요"
            indicator="선택"
            description="Apple을 선택하고 제출해보세요."
            errorMessage={Object.values(checkErrors).filter(Boolean).join(", ")}
          >
            <CheckSelectBox
              defaultChecked
              label="Apple"
              // formData를 위해 설정. controlled 사용 시 불필요
              inputProps={{ name: "fruit", value: "apple" }}
              invalid={!!checkErrors.apple}
              suffix={<CheckSelectBoxCheckmark />}
              footer={
                <Box px="x5" pb="x4">
                  <Text textStyle="t4Medium">
                    Apple을 선택하고 제출하면 에러 메시지가 표시됩니다.
                  </Text>
                </Box>
              }
            />
            <CheckSelectBox
              label="Melon"
              inputProps={{ name: "fruit", value: "melon" }}
              invalid={!!checkErrors.melon}
              suffix={<CheckSelectBoxCheckmark />}
            />
            <CheckSelectBox
              label="Mango"
              inputProps={{ name: "fruit", value: "mango" }}
              invalid={!!checkErrors.mango}
              suffix={<CheckSelectBoxCheckmark />}
            />
          </CheckSelectBoxGroup>
          <ActionButton type="submit" variant="neutralSolid">
            제출
          </ActionButton>
        </form>
      </VStack>

      <VStack asChild gap="spacingY.componentDefault" style={{ flex: 1 }}>
        <form onSubmit={handleRadioSubmit}>
          <RadioSelectBoxRoot
            label="선호하는 색상을 선택하세요"
            labelWeight="bold"
            showRequiredIndicator
            description="Red를 선택하고 제출해보세요."
            name="color"
            defaultValue="red"
            invalid={!!radioErrorMessage}
            errorMessage={radioErrorMessage}
          >
            <RadioSelectBoxItem
              value="red"
              label="Red"
              suffix={<RadioSelectBoxRadiomark />}
              footer={
                <Box px="x5" pb="x4">
                  <Text textStyle="t4Medium">
                    Red를 선택하고 제출하면 에러 메시지가 표시됩니다.
                  </Text>
                </Box>
              }
            />
            <RadioSelectBoxItem
              value="blue"
              label="Blue"
              suffix={<RadioSelectBoxRadiomark />}
              disabled
            />
            <RadioSelectBoxItem value="green" label="Green" suffix={<RadioSelectBoxRadiomark />} />
          </RadioSelectBoxRoot>
          <ActionButton type="submit" variant="neutralSolid">
            제출
          </ActionButton>
        </form>
      </VStack>
    </HStack>
  );
}

Last updated on