Radio

Data Input

하나의 선택지를 나타내는 라디오.

Usage

여러 옵션 중 하나만 선택(상호 배타). 2~N개 단일 선택

import

import

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

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

@mildang/icons 를 따로 설치한다. DS 패키지에 아이콘 컴포넌트가 포함되지 않는다.

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

API Reference

Radio Props

Prop

Type

Default

aria-label

string

지정 안 함

className

string

지정 안 함

description

ReactNode

지정 안 함

descriptionOrientation

"vertical" | "horizontal"

horizontal

disabled

boolean

false

endIcon

ReactNode

지정 안 함

label

ReactNode

지정 안 함

panda

SystemStyleObject

지정 안 함

size

RadioSize

medium

value

string

지정 안 함

RadioIndicator

Prop

Type

Default

disabled

boolean

false

size

"sm" | "md"

md

같은 패밀리

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

표시 유형

Radio 는 네 가지 표시 유형을 지원한다. 유형은 label·endIcon·description 조합으로 정해지고 별도 variant prop 은 없다.

  • label 만 — 가장 기본형.
  • label + endIcon — 라벨 뒤에 보조 아이콘(정보·경고 등)을 붙인다.
  • aria-label 만(icon only) — label 을 생략하면 원 인디케이터만 남으므로 aria-label 이 필수다.
  • descriptiondescriptionOrientation 으로 가로 한 줄(기본) 또는 세로 스택을 고른다.

표시 유형

label만 / label+endIcon / icon만(aria-label 필수) / description 을 나란히 둡니다. 넷 다 RadioGroup 안에서 씁니다.

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

// Radio 는 Radix 특성상 항상 RadioGroup 안에서만 동작한다. 다만 이 컴포넌트 스토리에서는
// "옵션 하나"임을 강조하기 위해 옵션을 1개만 노출한다. (여러 개를 묶는 예시는 RadioGroup 스토리 참고)

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

const sizeRow = css({ display: 'flex', alignItems: 'center', gap: 16 });

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

// 단일 Radio 를 sm/md 두 사이즈로 보여준다 (선택 그룹이 아니라 개별 옵션의 크기 비교).
const SizeSingles = (args: RadioProps) => (
  <div className={sizeStack}>
    {(['sm', 'md'] as const).map((size) => (
      <div key={size} className={sizeRow}>
        <span className={sizeTag}>{size}</span>
        <RadioGroup defaultValue="a" aria-label={`Radio ${size}`}>
          <Radio {...args} size={size} value="a" />
        </RadioGroup>
      </div>
    ))}
  </div>
);

const STORY_DEFAULT_ARGS = { ...({}), ...({
    disabled: false,
  }) } as ComponentProps<typeof Radio>;

const RadioIconOnlyExampleRender = (args: ComponentProps<typeof Radio>) => <SizeSingles {...args} aria-label="Option" />;

export default function RadioIconOnlyExample(props: Partial<ComponentProps<typeof Radio>>) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Radio>;
  return RadioIconOnlyExampleRender(mergedProps);
}

예제

기본 사용

라디오 두 개를 RadioGroup 으로 감싼 최소 형태 — 감싸지 않으면 선택 상태를 들 곳이 없어 렌더가 죽습니다.

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

const STORY_DEFAULT_ARGS = { ...({}), ...({
    size: 'md',
    label: 'Option',
    disabled: false,
    descriptionOrientation: 'horizontal',
  }) } as ComponentProps<typeof Radio>;

const RadioDefaultExampleRender = (args: ComponentProps<typeof Radio>) => (
    <RadioGroup aria-label="Radio">
      <Radio {...args} value="a" />
    </RadioGroup>
  );

export default function RadioDefaultExample(props: Partial<ComponentProps<typeof Radio>>) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Radio>;
  return RadioDefaultExampleRender(mergedProps);
}

개별 옵션 비활성

옵션 하나만 disabled 로 잠급니다. 그룹 전체를 잠그는 방법은 RadioGroup 의 상태 예제에 있습니다.

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

// 모든 상태(inactive/hover/active/disabled/active-disabled)를 한 화면에 나열 — 문서 States 섹션 + 비주얼 스냅샷용
const stateCell = css({ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 12 });

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

const RadioAllStatesExample = () => (
    <div
      id="radio-states"
      className={css({
        display: 'flex',
        gap: 32,
        flexWrap: 'wrap',
        alignItems: 'flex-start',
        padding: '8px',
      })}
    >
      <div className={stateCell}>
        <RadioGroup value="__none__" aria-label="Inactive state">
          <Radio value="a" label="Option" />
        </RadioGroup>
        <span className={stateCaption}>Inactive</span>
      </div>
      <div className={stateCell}>
        <RadioGroup aria-label="Hover state">
          <Radio value="a" label="Option" className="state-hover" />
        </RadioGroup>
        <span className={stateCaption}>Hover</span>
      </div>
      <div className={stateCell}>
        <RadioGroup value="a" aria-label="Active state">
          <Radio value="a" label="Option" />
        </RadioGroup>
        <span className={stateCaption}>Active</span>
      </div>
      <div className={stateCell}>
        <RadioGroup value="__none__" aria-label="Disabled state">
          <Radio value="a" label="Option" disabled />
        </RadioGroup>
        <span className={stateCaption}>Disabled</span>
      </div>
      <div className={stateCell}>
        <RadioGroup value="a" aria-label="Active disabled state">
          <Radio value="a" label="Option" disabled />
        </RadioGroup>
        <span className={stateCaption}>Active Disabled</span>
      </div>
    </div>
  );

export default RadioAllStatesExample;

WithEndIcon

import type { ComponentProps } from 'react';
import { Radio, RadioGroup, type RadioProps } from '@mildang/design-system/Radio';
import InfoOutline from '@mildang/icons/react/info-outline';
import { css } from '@mildang/styled-system/css';

// Radio 는 Radix 특성상 항상 RadioGroup 안에서만 동작한다. 다만 이 컴포넌트 스토리에서는
// "옵션 하나"임을 강조하기 위해 옵션을 1개만 노출한다. (여러 개를 묶는 예시는 RadioGroup 스토리 참고)

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

const sizeRow = css({ display: 'flex', alignItems: 'center', gap: 16 });

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

// 단일 Radio 를 sm/md 두 사이즈로 보여준다 (선택 그룹이 아니라 개별 옵션의 크기 비교).
const SizeSingles = (args: RadioProps) => (
  <div className={sizeStack}>
    {(['sm', 'md'] as const).map((size) => (
      <div key={size} className={sizeRow}>
        <span className={sizeTag}>{size}</span>
        <RadioGroup defaultValue="a" aria-label={`Radio ${size}`}>
          <Radio {...args} size={size} value="a" />
        </RadioGroup>
      </div>
    ))}
  </div>
);

const STORY_DEFAULT_ARGS = { ...({}), ...({
    label: 'Option',
  }) } as ComponentProps<typeof Radio>;

const RadioWithEndIconExampleRender = (args: ComponentProps<typeof Radio>) => <SizeSingles {...args} endIcon={<InfoOutline />} />;

export default function RadioWithEndIconExample(props: Partial<ComponentProps<typeof Radio>>) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Radio>;
  return RadioWithEndIconExampleRender(mergedProps);
}

Description (vertical / 세로 스택)

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

// Radio 는 Radix 특성상 항상 RadioGroup 안에서만 동작한다. 다만 이 컴포넌트 스토리에서는
// "옵션 하나"임을 강조하기 위해 옵션을 1개만 노출한다. (여러 개를 묶는 예시는 RadioGroup 스토리 참고)

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

const sizeRow = css({ display: 'flex', alignItems: 'center', gap: 16 });

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

// 단일 Radio 를 sm/md 두 사이즈로 보여준다 (선택 그룹이 아니라 개별 옵션의 크기 비교).
const SizeSingles = (args: RadioProps) => (
  <div className={sizeStack}>
    {(['sm', 'md'] as const).map((size) => (
      <div key={size} className={sizeRow}>
        <span className={sizeTag}>{size}</span>
        <RadioGroup defaultValue="a" aria-label={`Radio ${size}`}>
          <Radio {...args} size={size} value="a" />
        </RadioGroup>
      </div>
    ))}
  </div>
);

const STORY_DEFAULT_ARGS = { ...({}), ...({
    label: 'Label',
    description: 'Description',
    descriptionOrientation: 'vertical',
    disabled: false,
  }) } as ComponentProps<typeof Radio>;

const RadioDescriptionVerticalExampleRender = (args: ComponentProps<typeof Radio>) => <SizeSingles {...args} />;

export default function RadioDescriptionVerticalExample(props: Partial<ComponentProps<typeof Radio>>) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Radio>;
  return RadioDescriptionVerticalExampleRender(mergedProps);
}

Description (horizontal / 한 줄)

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

// Radio 는 Radix 특성상 항상 RadioGroup 안에서만 동작한다. 다만 이 컴포넌트 스토리에서는
// "옵션 하나"임을 강조하기 위해 옵션을 1개만 노출한다. (여러 개를 묶는 예시는 RadioGroup 스토리 참고)

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

const sizeRow = css({ display: 'flex', alignItems: 'center', gap: 16 });

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

// 단일 Radio 를 sm/md 두 사이즈로 보여준다 (선택 그룹이 아니라 개별 옵션의 크기 비교).
const SizeSingles = (args: RadioProps) => (
  <div className={sizeStack}>
    {(['sm', 'md'] as const).map((size) => (
      <div key={size} className={sizeRow}>
        <span className={sizeTag}>{size}</span>
        <RadioGroup defaultValue="a" aria-label={`Radio ${size}`}>
          <Radio {...args} size={size} value="a" />
        </RadioGroup>
      </div>
    ))}
  </div>
);

const STORY_DEFAULT_ARGS = { ...({}), ...({
    label: 'Label',
    description: 'Description',
    descriptionOrientation: 'horizontal',
    disabled: false,
  }) } as ComponentProps<typeof Radio>;

const RadioDescriptionHorizontalExampleRender = (args: ComponentProps<typeof Radio>) => <SizeSingles {...args} />;

export default function RadioDescriptionHorizontalExample(props: Partial<ComponentProps<typeof Radio>>) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Radio>;
  return RadioDescriptionHorizontalExampleRender(mergedProps);
}