ComponentsP3 본문

RadioGroup

APG radio group 패턴 — roving tabindex로 그룹이 탭 순서에 한 번만 잡히고, 화살표 키가 순환 이동하며 즉시 선택합니다. 비제어(defaultValue) 우선, value로 제어 모드도 지원.

마지막 업데이트 2026-06-11

한눈에#

상호배타 옵션을 모두 펼쳐 한눈에 비교시키는 APG radio group입니다 — Tab으로 그룹에 한 번 진입하고 화살표로 순환 이동하며 즉시 선택합니다.

  • APG radio group
  • roving tabindex
  • 화살표 순환 선택
  • 2~5 옵션 권장

그룹이 탭 순서에 한 번만 잡히고(roving), 비활성 옵션은 화살표 이동이 건너뜁니다

사용 시점#

상호배타 옵션 2~5개를 펼쳐 비교시키면 RadioGroup — 길거나 세그먼트·토글이면 다른 컴포넌트입니다.

쓴다

요금제·공개 범위처럼 상호배타 옵션 2~5개를 한눈에 펼쳐 비교

대신 Select

옵션이 6개 이상으로 길어 접어야 할 때

대신 SegmentedButton

연결된 세그먼트 컨트롤 모양이 더 맞을 때

대신 Switch

단일 on/off 토글일 때

플레이그라운드#

컨트롤로 props를 조작하면 미리보기와 코드가 실시간 갱신됩니다.

import { RadioGroup } from '@wds/ui-web';

<RadioGroup
  aria-label="요금제"
  options={[
    { value: 'basic', label: '베이직' },
    { value: 'pro', label: '프로' },
    { value: 'team', label: '팀' },
  ]}
/>

해부#

한 항목의 컨트롤·라벨·히트 영역 위에 토큰 실측을 핀으로 얹습니다.

md · 20px 원형 컨트롤 + body-1 라벨 — 한 항목의 실측
control
20pxicon.size-md
dot
≈50% (inset 25%)
border
1pxborder-strong
gap
8pxspace-2
label
16pxbody-1
row min-height
32pxspace-8
hit area
44pxcontrol.height-md
focus radius
6pxradius.sm

변형#

비활성 옵션 — 화살표 이동이 건너뜁니다

크기#

size로 2단(ADR-012 B-P2) — md(기본) icon.size-md 컨트롤 + body-1 라벨, sm icon.size-sm 컨트롤 + body-2 라벨. 항목 최소 높이 space.8(32px)는 공통이라 터치 타깃을 유지합니다.

md · sm — 컨트롤·라벨 크기차 (행 최소높이·히트영역 공통)
sizecontrollabelrow min-heighthit areamd20px (icon.size-md)16px (body-1)32px44pxsm16px (icon.size-sm)14px (body-2)32px44px

상태#

  • Selectedaria-checked + color.primary 링과 도트
  • Hover — 비선택 컨트롤 보더가 color.border-strong으로 진해짐
  • Focus — 항목 :focus-visible에 2px primary 아웃라인
  • Disabled — 옵션 단위 disabled — 커서 차단 + color.text-placeholder 잉크

제어/비제어 — 기본은 비제어(defaultValue)이며 내부에서 선택 상태를 소유합니다. value를 주면 제어 모드가 되어 부모가 상태를 소유하고, 클릭·화살표 키 등 모든 선택 전환은 onChange(value)로 통지됩니다(렌더는 부모 value를 따르며, 같은 값 재선택은 발화하지 않음).

Props#

Prop타입기본값설명
optionsReadonlyArray<RadioOption>{ value, label, disabled? } 배열 — 데이터 prop 방식
valuestring제어 선택 value — 제공하면 제어 모드(내부 상태 무시, 모든 전환이 onChange로 통지)
defaultValuestring초기 선택 value — 이후 상태는 내부 소유(비제어, value 미제공 시)
onChange(value: string) => void선택 변경 콜백 — 같은 값 재선택은 발화하지 않음
size'sm' | 'md''md'컨트롤 크기 — md(icon.size-md+body-1) / sm(icon.size-sm+body-2)
…restOmit<HTMLAttributes<HTMLDivElement>, 'onChange' | 'defaultValue'>aria-label 등은 radiogroup 루트로 전달

접근성#

루트가 role="radiogroup", 각 항목이 button[role="radio"] + aria-checked입니다. 그룹 이름은 aria-label로 부여하세요.

동작
Tab그룹 진입/이탈 — 선택(없으면 첫 활성) 항목만 탭 순서에 포함(roving tabindex)
/ 다음 활성 항목으로 순환 이동 + 즉시 선택 + 포커스
/ 이전 활성 항목으로 순환 이동 + 즉시 선택 + 포커스
Space / Enter네이티브 button 활성화 — 현재 항목 선택

화살표 이동은 비활성 옵션을 건너뛰고, 끝에서 반대쪽 끝으로 순환합니다.

토큰#

component 토큰 없이 semantic을 직접 소비합니다(신설 기준 §4 미충족).

속성토큰
컨트롤icon.size-md 원형 + color.surface 배경
보더color.border → hover color.border-strong
선택color.primary 링/도트
라벨font.size-body-1 · color.text
비활성color.surface-muted + color.text-placeholder
포커스 링color.primary 2px 아웃라인 + radius.sm
간격space.2 · space.3 · 최소 높이 space.8
모션duration.fast + ease.standard