RadioGroup

Data Input

여러 라디오를 묶어 하나만 선택하게 하는 그룹.

Usage

배타 선택 묶음. 자식으로 Radio(기본)·RadioBox·RadioCard 중 하나를 골라 밀도를 정한다

import

import

import { RadioGroup } from '@mildang/design-system/Radio';

예제를 복사해 쓸 때 필요한 준비

@mildang/styled-system 은 이 저장소에서 Panda 가 생성하는 산출물이다. 저장소 안에서는 turbo run ship 이후 쓸 수 있고, 패키지 소비자는 자기 Panda 산출물이나 다른 레이아웃 수단으로 바꿔야 한다.

API Reference

RadioGroup Props

Prop

Type

Default

defaultValue

string | (string & readonly string[])

지정 안 함

dir

"ltr" | "rtl"

지정 안 함

disabled

boolean

지정 안 함

errorMessage

ReactNode

지정 안 함

invalid

boolean

지정 안 함

name

string

지정 안 함

onValueChange

(value: string) => void

지정 안 함

orientation

"horizontal" | "vertical"

vertical

panda

SystemStyleObject

지정 안 함

required

boolean

지정 안 함

value

string

지정 안 함

같은 패밀리

@mildang/design-system/Radio 에서 같이 내보내는 컴포넌트다.

구성 구조

Radio 는 단독으로 쓰지 않는다. 항상 RadioGroup 으로 감싸 그룹으로 사용하며, 선택 상태·배치 방향(orientation)·키보드 이동은 RadioGroup 이 관리한다 — 감싸지 않으면 RadioGroupItem must be used within RadioGroup 에러로 렌더가 죽는다. 최소 조합 코드는 아래 "기본 사용" 예제를 참고한다.

  • RadioGroup — 컨테이너(필수). value/defaultValue·orientation·disabled 등 그룹 동작을 담당한다.
  • Radio — 개별 옵션. label·endIcon·description 등 표시를 담당한다.
  • RadioBox — 썸네일·설명을 담는 박스형 옵션. RadioGroup 안에서 Radio 대신 쓴다.
  • RadioCard — 제목·설명이 많은 카드형 옵션. RadioGroup 안에서 Radio 대신 쓴다.

여러 항목을 동시에 골라야 하면 Radio 가 아니라 Checkbox 다 — 구분 기준은 Checkbox 문서의 "사용 목적과 선택 기준" 절 하나에서만 다룬다.

기본 사용

세로 배치에 항목별 설명까지 붙인 가장 흔한 형태 — 그룹 자체는 컨텍스트와 배치만 담당합니다.

import type { ComponentProps } from 'react';
import { Radio, RadioGroup } from '@mildang/design-system/Radio';

const STORY_DEFAULT_ARGS = { ...({}), ...({
    orientation: 'vertical',
    disabled: false,
    required: false,
  }) } as ComponentProps<typeof RadioGroup>;

const RadioGroupDefaultExampleRender = (args: ComponentProps<typeof RadioGroup>) => (
    <RadioGroup {...args} defaultValue="opt1" gap="16">
      <Radio value="opt1" label="Option 1" />
      <Radio value="opt2" label="Option 2" />
      <Radio value="opt3" label="Option 3" />
    </RadioGroup>
  );

export default function RadioGroupDefaultExample(props: Partial<ComponentProps<typeof RadioGroup>>) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof RadioGroup>;
  return RadioGroupDefaultExampleRender(mergedProps);
}

예제

그룹 상태

그룹 단위 상태입니다. disabled 는 자식 전체를 잠그고, invalid 와 errorMessage 는 아래에 HelpText 를 붙입니다.

import type { ComponentProps } from 'react';
import { Radio, RadioGroup } from '@mildang/design-system/Radio';

const STORY_DEFAULT_ARGS = { ...({}), ...({
    orientation: 'vertical',
    required: true,
    invalid: true,
    errorMessage: '하나를 선택해주세요.',
  }) } as ComponentProps<typeof RadioGroup>;

const RadioGroupInvalidExampleRender = (args: ComponentProps<typeof RadioGroup>) => (
    <RadioGroup {...args} gap="16">
      <Radio value="opt1" label="Option 1" />
      <Radio value="opt2" label="Option 2" />
      <Radio value="opt3" label="Option 3" />
    </RadioGroup>
  );

export default function RadioGroupInvalidExample(props: Partial<ComponentProps<typeof RadioGroup>>) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof RadioGroup>;
  return RadioGroupInvalidExampleRender(mergedProps);
}

With RadioBox

import type { ComponentProps } from 'react';
import { RadioBox, RadioGroup } from '@mildang/design-system/Radio';

const STORY_DEFAULT_ARGS = { ...({}), ...({
    orientation: 'horizontal',
    disabled: false,
    required: false,
  }) } as ComponentProps<typeof RadioGroup>;

const RadioGroupWithRadioBoxExampleRender = (args: ComponentProps<typeof RadioGroup>) => (
    <RadioGroup {...args} defaultValue="a" gap="16">
      <RadioBox value="a" title="Option A" description="Description" />
      <RadioBox value="b" title="Option B" description="Description" />
      <RadioBox value="c" title="Option C" description="Description" />
    </RadioGroup>
  );

export default function RadioGroupWithRadioBoxExample(props: Partial<ComponentProps<typeof RadioGroup>>) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof RadioGroup>;
  return RadioGroupWithRadioBoxExampleRender(mergedProps);
}

With RadioCard

import type { ComponentProps } from 'react';
import { RadioCard, RadioGroup } from '@mildang/design-system/Radio';

const STORY_DEFAULT_ARGS = { ...({}), ...({
    orientation: 'vertical',
    disabled: false,
    required: false,
  }) } as ComponentProps<typeof RadioGroup>;

const RadioGroupWithRadioCardExampleRender = (args: ComponentProps<typeof RadioGroup>) => (
    <RadioGroup {...args} defaultValue="a" gap="16">
      <RadioCard value="a" label="Option A" description="Description" />
      <RadioCard value="b" label="Option B" description="Description" />
      <RadioCard value="c" label="Option C" description="Description" />
    </RadioGroup>
  );

export default function RadioGroupWithRadioCardExample(props: Partial<ComponentProps<typeof RadioGroup>>) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof RadioGroup>;
  return RadioGroupWithRadioCardExampleRender(mergedProps);
}

Radio — layouts

import { Radio, RadioGroup } from '@mildang/design-system/Radio';
import { css } from '@mildang/styled-system/css';

// 레이아웃 쇼케이스 헬퍼.
// 핵심: Radio 류 컴포넌트는 자체 고정 너비가 없다 — 폭은 항상 부모가 결정한다.
const layoutStack = css({ display: 'flex', flexDirection: 'column', gap: 32 });

const layoutSection = css({ display: 'flex', flexDirection: 'column', gap: 8 });

const layoutCaption = css({ textStyle: 'caption-md-medium', color: 'neutral.text.lowest' });

const RadioGroupRadioLayoutsExample = () => (
    <div className={layoutStack}>
      <div className={layoutSection}>
        <span className={layoutCaption}>Vertical (기본) — 세로 스택, 각 항목은 라벨 폭</span>
        <RadioGroup defaultValue="opt1" orientation="vertical" aria-label="Radio vertical" gap="12">
          <Radio value="opt1" label="Option 1" />
          <Radio value="opt2" label="Option 2" />
          <Radio value="opt3" label="Option 3" />
        </RadioGroup>
      </div>
      <div className={layoutSection}>
        <span className={layoutCaption}>Horizontal — 가로 나열</span>
        <RadioGroup defaultValue="opt1" orientation="horizontal" aria-label="Radio horizontal" gap="16">
          <Radio value="opt1" label="Option 1" />
          <Radio value="opt2" label="Option 2" />
          <Radio value="opt3" label="Option 3" />
        </RadioGroup>
      </div>
      <div className={layoutSection}>
        <span className={layoutCaption}>
          부모에 width 320 을 줘도 — Radio 는 라벨 폭으로 hug (늘어나지 않음)
        </span>
        <RadioGroup
          defaultValue="opt1"
          orientation="vertical"
          aria-label="Radio parent width"
          width={320}
          gap="12"
        >
          <Radio value="opt1" label="Option 1" />
          <Radio value="opt2" label="Option 2" />
        </RadioGroup>
      </div>
    </div>
  );

export default RadioGroupRadioLayoutsExample;

RadioBox — layouts

import { RadioBox, RadioGroup } from '@mildang/design-system/Radio';
import { css } from '@mildang/styled-system/css';

// 레이아웃 쇼케이스 헬퍼.
// 핵심: Radio 류 컴포넌트는 자체 고정 너비가 없다 — 폭은 항상 부모가 결정한다.
const layoutStack = css({ display: 'flex', flexDirection: 'column', gap: 32 });

const layoutSection = css({ display: 'flex', flexDirection: 'column', gap: 8 });

const layoutCaption = css({ textStyle: 'caption-md-medium', color: 'neutral.text.lowest' });

// 같은 컴포넌트를 서로 다른 부모 폭에 나란히 두고 폭이 달라지는 것을 비교하는 행.
const widthCompareRow = css({ display: 'flex', gap: 16, alignItems: 'flex-start', flexWrap: 'wrap' });

const RadioGroupRadioBoxLayoutsExample = () => (
    <div className={layoutStack}>
      <div className={layoutSection}>
        <span className={layoutCaption}>① 너비 미지정 · Vertical — 부모(RadioGroup) 폭을 그대로 채운다</span>
        <RadioGroup defaultValue="a" orientation="vertical" aria-label="RadioBox fill" gap="12">
          <RadioBox value="a" title="Option A" description="Description" />
          <RadioBox value="b" title="Option B" description="Description" />
        </RadioGroup>
      </div>
      <div className={layoutSection}>
        <span className={layoutCaption}>
          ② 부모 폭에 따라 달라진다 — 같은 RadioBox, 부모 width 만 200 / 320 으로 다름
        </span>
        <div className={widthCompareRow}>
          <RadioGroup
            defaultValue="a"
            orientation="vertical"
            aria-label="RadioBox width 200"
            width={200}
            gap="12"
          >
            <RadioBox value="a" title="Option A" description="Description" />
            <RadioBox value="b" title="Option B" description="Description" />
          </RadioGroup>
          <RadioGroup
            defaultValue="a"
            orientation="vertical"
            aria-label="RadioBox width 320"
            width={320}
            gap="12"
          >
            <RadioBox value="a" title="Option A" description="Description" />
            <RadioBox value="b" title="Option B" description="Description" />
          </RadioGroup>
        </div>
      </div>
      <div className={layoutSection}>
        <span className={layoutCaption}>③ Horizontal — 한 줄에 콘텐츠 폭으로 나열</span>
        <RadioGroup defaultValue="a" orientation="horizontal" aria-label="RadioBox horizontal" gap="12">
          <RadioBox value="a" title="Option A" description="Description" />
          <RadioBox value="b" title="Option B" description="Description" />
          <RadioBox value="c" title="Option C" description="Description" />
        </RadioGroup>
      </div>
    </div>
  );

export default RadioGroupRadioBoxLayoutsExample;

RadioCard — layouts

import { RadioCard, RadioGroup } from '@mildang/design-system/Radio';
import { css } from '@mildang/styled-system/css';

// 레이아웃 쇼케이스 헬퍼.
// 핵심: Radio 류 컴포넌트는 자체 고정 너비가 없다 — 폭은 항상 부모가 결정한다.
const layoutStack = css({ display: 'flex', flexDirection: 'column', gap: 32 });

const layoutSection = css({ display: 'flex', flexDirection: 'column', gap: 8 });

const layoutCaption = css({ textStyle: 'caption-md-medium', color: 'neutral.text.lowest' });

// 같은 컴포넌트를 서로 다른 부모 폭에 나란히 두고 폭이 달라지는 것을 비교하는 행.
const widthCompareRow = css({ display: 'flex', gap: 16, alignItems: 'flex-start', flexWrap: 'wrap' });

const RadioGroupRadioCardLayoutsExample = () => (
    <div className={layoutStack}>
      <div className={layoutSection}>
        <span className={layoutCaption}>① 너비 미지정 · Vertical — 부모(RadioGroup) 폭을 그대로 채운다</span>
        <RadioGroup defaultValue="a" orientation="vertical" aria-label="RadioCard fill" gap="12">
          <RadioCard value="a" label="Option A" description="Description" />
          <RadioCard value="b" label="Option B" description="Description" />
        </RadioGroup>
      </div>
      <div className={layoutSection}>
        <span className={layoutCaption}>
          ② 부모 폭에 따라 달라진다 — 같은 RadioCard, 부모 width 만 200 / 320 으로 다름
        </span>
        <div className={widthCompareRow}>
          <RadioGroup
            defaultValue="a"
            orientation="vertical"
            aria-label="RadioCard width 200"
            width={200}
            gap="12"
          >
            <RadioCard value="a" label="Option A" description="Description" />
            <RadioCard value="b" label="Option B" description="Description" />
          </RadioGroup>
          <RadioGroup
            defaultValue="a"
            orientation="vertical"
            aria-label="RadioCard width 320"
            width={320}
            gap="12"
          >
            <RadioCard value="a" label="Option A" description="Description" />
            <RadioCard value="b" label="Option B" description="Description" />
          </RadioGroup>
        </div>
      </div>
      <div className={layoutSection}>
        <span className={layoutCaption}>③ Horizontal — 한 줄에 콘텐츠 폭으로 나열</span>
        <RadioGroup defaultValue="a" orientation="horizontal" aria-label="RadioCard horizontal" gap="12">
          <RadioCard value="a" label="Option A" description="Description" />
          <RadioCard value="b" label="Option B" description="Description" />
          <RadioCard value="c" label="Option C" description="Description" />
        </RadioGroup>
      </div>
    </div>
  );

export default RadioGroupRadioCardLayoutsExample;