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
string
지정 안 함
string
지정 안 함
ReactNode
지정 안 함
"vertical" | "horizontal"
horizontal
boolean
false
ReactNode
지정 안 함
ReactNode
지정 안 함
SystemStyleObject
지정 안 함
RadioSize
medium
string
지정 안 함
RadioIndicator
Prop
Type
Default
boolean
false
"sm" | "md"
md
같은 패밀리
@mildang/design-system/Radio 에서 같이 내보내는 컴포넌트다.
표시 유형
Radio 는 네 가지 표시 유형을 지원한다. 유형은 label·endIcon·description 조합으로
정해지고 별도 variant prop 은 없다.
label만 — 가장 기본형.label+endIcon— 라벨 뒤에 보조 아이콘(정보·경고 등)을 붙인다.aria-label만(icon only) —label을 생략하면 원 인디케이터만 남으므로aria-label이 필수다.description—descriptionOrientation으로 가로 한 줄(기본) 또는 세로 스택을 고른다.
표시 유형
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);
}