Groo Design

Radio Group

여러 옵션 가운데 하나만 고르게 하는 컴포넌트. 선택지를 모두 펼쳐 두고 저장하기 전에 하나를 고를 때 쓴다.

구조는 당근 SEED Radio(Apache-2.0)를 따른다 — 동그라미(Radiomark) · 동그라미 + 라벨(Radio) · 묶음(Radio Group). 값은 groo 토큰이고, SEED 와 다른 자리는 맨 아래 "SEED 와 다른 점" 에 적었다(2026-09-30 사용자 결정).

수치 원본은 radio-group.yaml 이다. 수치 표 자리([표: …])와 그림 자리([그림: …])는 사이트가 그 파일로 그린다 — GitHub 에서는 그 파일 · 그림 코드로 가는 링크로 보인다.

neutral
brand
비활성
neutral
brand
비활성
선택 안 됨 · 선택 — neutral · brand, 위는 라이트 아래는 다크

직접 골라 보기

크기 · 톤 · 굵기 · 상태를 고르면 스펙대로 그린 Radio 와 그 코드가 바뀐다. 묶음 안에서 실제로 눌러 볼 수 있다.

크기 size
톤 tone
굵기 weight
처음 상태
비활성
모드
브랜드
import { Radio, RadioGroup } from "@/components/ui/radio-group"

<RadioGroup defaultValue="none" aria-label="반복">
  <Radio value="none" label="반복 없음" />
  <Radio value="daily" label="매일" />
  <Radio value="weekly" label="매주" />
</RadioGroup>

Anatomy

ⓐⓑⓒ라벨ⓓ
ⓐ Radiomarkⓑ Dotⓒ Labelⓓ Focus ring
Radio 는 동그라미(Radiomark)와 라벨로 이뤄진다 — 동그라미는 따로 떼어 쓸 수 있다
부위설명
ⓐ Radiomark동그라미 — 배경 · 테두리. 라벨을 따로 짜는 자리(Select Box 의 오른쪽 동그라미 같은)에서 따로 쓴다.
ⓑ Dot가운데 점 — 선택을 나타낸다. 선택 안 됨에는 보이지 않는다.
ⓒ Label무엇을 고르는지. 동그라미와 함께 눌린다.
ⓓ Focus ring키보드 포커스에만 2px 링 · 2px 띄움(v106).

부위

부위설명
rootRadio 한 줄 — 동그라미 + 라벨, 누르는 영역
radiomark동그라미(Radiomark) — 배경 · 테두리. 라벨이 따로 서는 자리에는 이것만 넣어 쓴다
dot가운데 점 — 선택을 나타낸다
label글자(Label)
focusRing키보드 포커스 링(웹 · 앱 — 키보드로 옮길 때만)
group묶음(Radio Group) — 세로로 쌓는다

Properties

Size

두 가지다. 동그라미 · 점 · 라벨 · 줄 높이가 함께 정해진다 — Checkbox 와 같은 크기라 한 폼에 함께 서도 줄이 맞는다.

  • medium(동그라미 20 · 점 8 · 라벨 14 · 줄 32) — 기본. 화면 안의 폼 · 설정.
  • large(동그라미 24 · 점 10 · 라벨 16 · 줄 36) — 한 화면의 중심 선택, 모바일에서 홀로 서는 선택.

누르는 영역은 라벨까지 묶어 가로 · 세로 44 까지 넓힌다(기초 Inclusive — 44 는 반드시).

medium동그라미 20 · 점 8 · 라벨 14px · 줄 32
large동그라미 24 · 점 10 · 라벨 16px · 줄 36
크기 두 가지 — 동그라미 · 라벨 · 줄 높이

크기

size최소 높이radiomark 크기dot 크기글자설명
medium (기본)32px20px8pxtext-t4 14px / 19px동그라미 20 · 라벨 14 — 기본
large36px24px10pxtext-t5 16px / 22px동그라미 24 · 라벨 16 — 한 화면의 중심 선택 · 모바일 단독

모든 조합에 공통인 값:

공통

부위상태속성값비고
rootenabled간격spacing-x2 8px
rootenabled커서pointer
rootenabled줄 맞춤flex-start줄은 동그라미 + 라벨만큼만 차지한다 — 묶음 폭으로 늘이지 않는다(직접 짠 줄은 묶음 폭을 쓴다)
rootenabled터치 영역44 × 44라벨까지 묶어 44 — 라벨이 따로 서는 자리에서 동그라미만 쓰면 그 줄 전체가 누르는 영역(SEED touch target · 기초 Inclusive)
radiomarkenabled모서리radius-full 9999px
radiomarkenabled전환 대상background-color, border-color
radiomarkenabled전환 시간motion-duration-color-transition 150ms
radiomarkenabled전환 곡선motion-ease-easing cubic-bezier(0.35, 0, 0.35, 1)
dotenabled모서리radius-full 9999px
dotenabled전환 대상background-color
dotenabled전환 시간motion-duration-color-transition 150ms
dotenabled전환 곡선motion-ease-easing cubic-bezier(0.35, 0, 0.35, 1)
labelenabled글꼴font-sans
labelenabled글자·아이콘color-fg-neutral #1A1F2E · 다크 #F5F6FA
groupenabled간격spacing-x3 12px묶음 안 줄 사이(세로) — 줄 32 · 36 에 12 를 더해 44 · 48 마다 한 줄이라 이웃 줄과 누르는 영역(44)이 겹치지 않는다(사용자 결정 — SEED 는 4). 가로 배치는 두지 않는다
focusRingfocused링 두께2px웹 · 앱 같다 — 앱은 하드웨어 키보드로 옮길 때만 그린다(Flutter FocusHighlightMode.traditional), 손가락으로 누를 때는 없다(2026-10-08 — DESIGN.md State · Button 과 같다)
focusRingfocused링 간격2px
focusRingfocused링 색color-stroke-focus-ring #1A1F2E · 다크 #F5F6FA
radiomarkpressed배율세로 2px 축소동그라미만 준다 — 기준 길이 max(높이, 폭 ÷ 4, 24) 에서 2px(Feedback 의 눌림 피드백, v104)
radiomarkpressed축소 시간motion-duration-pressed-scale 150ms
radiomarkpressed축소 곡선motion-ease-pressed-scale cubic-bezier(0, 0, 0.15, 1)
rootdisabled커서not-allowed포인터 이벤트는 켜 둔다 — 끄면 이 커서가 안 보인다(SEED 와 같다)
labeldisabled글자·아이콘color-fg-disabled #8A91A0 · 다크 #838997

Weight

라벨 굵기다. 강조가 필요할 때 bold.

regular라벨 400(기본)
bold라벨 700 — 강조
라벨 굵기 — regular · bold

굵기

weight굵기설명
regular (기본)400라벨 400(기본)
bold700라벨 700 — 강조

Tone

선택했을 때의 색이다. neutral(짙은 회색)이 기본이고, 브랜드 색(brand)은 서비스 핵심 흐름에서만 쓴다 — Checkbox 와 같은 규칙이다(사용자 결정). SEED 도 Radio 는 Neutral 을 기본으로 둔다. 선택하면 테두리 없이 원을 채우고 가운데에 채움과 대비되는 점이 선다(neutral 은 fg-neutral-inverted, brand 는 흰색).

neutral기본 — 짙은 회색
brand · Desk서비스 핵심 흐름에서만
brand · HRHR 은 초록
톤 두 가지 — neutral · brand(Desk · HR)

State

선택 여부(선택 안 됨 · 선택)와 상호작용 상태가 곱해진다.

상태모습
enabled기본
hovered웹. 누름 색과 같다(v106), 축소는 없다
focused웹 · 앱. 키보드 포커스에만 링 2px · 띄움 2px(v106). 앱은 하드웨어 키보드로 옮길 때만 그리고 손가락으로 누를 때는 없다(2026-10-08)
pressed누름 색 + 동그라미 세로 2px 거리 축소(v104). 라벨은 줄지 않는다
disabled전용 색(bg-disabled · fg-disabled, v106). 선택도 채운 원 그대로 색만 바뀐다 — 불투명도로 흐리게 하지 않는다
neutral
기본
enabled
호버
hovered
포커스
focused
누름
pressed
비활성
disabled
선택 안 됨
선택
brand
기본
enabled
호버
hovered
포커스
focused
누름
pressed
비활성
disabled
선택 안 됨
선택
선택 여부 × 상태 — 호버 · 포커스 · 누름은 그 순간을 멈춰 그렸다

상태 매트릭스 — neutral · 선택 안 됨

상태커서radiomark 배경radiomark 테두리radiomark 테두리 두께radiomark 배율radiomark 축소 시간radiomark 축소 곡선dot 색label 글자·아이콘링 두께링 간격링 색
enabled 기본pointertransparentcolor-stroke-neutral-solid #767C8B · 다크 #838997 — 3:1 이상 — v109(SEED 는 옅은 선)1px———transparent — 선택 안 됨에는 점이 보이지 않는다color-fg-neutral #1A1F2E · 다크 #F5F6FA———
hovered 웹 — 누름 색과 같다(v106), 축소는 없다pointercolor-bg-layer-default-pressed #F7F8FD · 다크 #353B4Dcolor-stroke-neutral-solid #767C8B · 다크 #838997 — 3:1 이상 — v109(SEED 는 옅은 선)1px———transparent — 선택 안 됨에는 점이 보이지 않는다color-fg-neutral #1A1F2E · 다크 #F5F6FA———
focused 키보드 포커스에만 링 — 웹 · 앱(앱은 2026-10-08 부터, 손가락으로 누를 때는 없다)pointertransparentcolor-stroke-neutral-solid #767C8B · 다크 #838997 — 3:1 이상 — v109(SEED 는 옅은 선)1px———transparent — 선택 안 됨에는 점이 보이지 않는다color-fg-neutral #1A1F2E · 다크 #F5F6FA2px — 웹 · 앱 같다 — 앱은 하드웨어 키보드로 옮길 때만 그린다(Flutter FocusHighlightMode.traditional), 손가락으로 누를 때는 없다(2026-10-08 — DESIGN.md State · Button 과 같다)2pxcolor-stroke-focus-ring #1A1F2E · 다크 #F5F6FA
pressed 누름 색 + 동그라미 세로 2px 거리 축소(v104)pointercolor-bg-layer-default-pressed #F7F8FD · 다크 #353B4D — SEED bg.transparent-pressed 의 불투명 짝color-stroke-neutral-solid #767C8B · 다크 #838997 — 3:1 이상 — v109(SEED 는 옅은 선)1px세로 2px 축소 — 동그라미만 준다 — 기준 길이 max(높이, 폭 ÷ 4, 24) 에서 2px(Feedback 의 눌림 피드백, v104)motion-duration-pressed-scale 150msmotion-ease-pressed-scale cubic-bezier(0, 0, 0.15, 1)transparent — 선택 안 됨에는 점이 보이지 않는다color-fg-neutral #1A1F2E · 다크 #F5F6FA———
disabled 전용 색(v106) — 불투명도로 흐리게 하지 않는다not-allowed — 포인터 이벤트는 켜 둔다 — 끄면 이 커서가 안 보인다(SEED 와 같다)color-bg-disabled #F5F6FA · 다크 #353B4D — SEED palette.gray-300 자리color-stroke-neutral-weak #E5E8EF · 다크 #404757 — SEED stroke.neutral-weak(gray-400)1px———transparent — 선택 안 됨에는 점이 보이지 않는다color-fg-disabled #8A91A0 · 다크 #838997———

상태 매트릭스 — neutral · 선택

상태커서radiomark 배경radiomark 테두리 두께radiomark 배율radiomark 축소 시간radiomark 축소 곡선dot 색label 글자·아이콘링 두께링 간격링 색
enabled 기본pointercolor-bg-neutral-inverted #1A1F2E · 다크 #F5F6FA0px———color-fg-neutral-inverted #FFFFFF · 다크 #242938color-fg-neutral #1A1F2E · 다크 #F5F6FA———
hovered 웹 — 누름 색과 같다(v106), 축소는 없다pointercolor-bg-neutral-inverted-pressed #535866 · 다크 #B7BDCC0px———color-fg-neutral-inverted #FFFFFF · 다크 #242938color-fg-neutral #1A1F2E · 다크 #F5F6FA———
focused 키보드 포커스에만 링 — 웹 · 앱(앱은 2026-10-08 부터, 손가락으로 누를 때는 없다)pointercolor-bg-neutral-inverted #1A1F2E · 다크 #F5F6FA0px———color-fg-neutral-inverted #FFFFFF · 다크 #242938color-fg-neutral #1A1F2E · 다크 #F5F6FA2px — 웹 · 앱 같다 — 앱은 하드웨어 키보드로 옮길 때만 그린다(Flutter FocusHighlightMode.traditional), 손가락으로 누를 때는 없다(2026-10-08 — DESIGN.md State · Button 과 같다)2pxcolor-stroke-focus-ring #1A1F2E · 다크 #F5F6FA
pressed 누름 색 + 동그라미 세로 2px 거리 축소(v104)pointercolor-bg-neutral-inverted-pressed #535866 · 다크 #B7BDCC0px세로 2px 축소 — 동그라미만 준다 — 기준 길이 max(높이, 폭 ÷ 4, 24) 에서 2px(Feedback 의 눌림 피드백, v104)motion-duration-pressed-scale 150msmotion-ease-pressed-scale cubic-bezier(0, 0, 0.15, 1)color-fg-neutral-inverted #FFFFFF · 다크 #242938color-fg-neutral #1A1F2E · 다크 #F5F6FA———
disabled 전용 색(v106) — 불투명도로 흐리게 하지 않는다not-allowed — 포인터 이벤트는 켜 둔다 — 끄면 이 커서가 안 보인다(SEED 와 같다)color-bg-disabled #F5F6FA · 다크 #353B4D — Checkbox 와 같게 — 켜진 모양 그대로 색만(사용자 결정). SEED 는 채움을 빼고 테두리 + 커진 점0px———color-fg-disabled #8A91A0 · 다크 #838997color-fg-disabled #8A91A0 · 다크 #838997———

상태 매트릭스 — brand · 선택

상태커서radiomark 배경radiomark 테두리 두께radiomark 배율radiomark 축소 시간radiomark 축소 곡선dot 색label 글자·아이콘링 두께링 간격링 색
enabled 기본pointercolor-bg-brand-solid HR #075B3C · Desk #0147AD0px———color-static-white #FFFFFFcolor-fg-neutral #1A1F2E · 다크 #F5F6FA———
hovered 웹 — 누름 색과 같다(v106), 축소는 없다pointercolor-bg-brand-solid-pressed HR #064F33 · Desk #013D960px———color-static-white #FFFFFFcolor-fg-neutral #1A1F2E · 다크 #F5F6FA———
focused 키보드 포커스에만 링 — 웹 · 앱(앱은 2026-10-08 부터, 손가락으로 누를 때는 없다)pointercolor-bg-brand-solid HR #075B3C · Desk #0147AD0px———color-static-white #FFFFFFcolor-fg-neutral #1A1F2E · 다크 #F5F6FA2px — 웹 · 앱 같다 — 앱은 하드웨어 키보드로 옮길 때만 그린다(Flutter FocusHighlightMode.traditional), 손가락으로 누를 때는 없다(2026-10-08 — DESIGN.md State · Button 과 같다)2pxcolor-stroke-focus-ring #1A1F2E · 다크 #F5F6FA
pressed 누름 색 + 동그라미 세로 2px 거리 축소(v104)pointercolor-bg-brand-solid-pressed HR #064F33 · Desk #013D960px세로 2px 축소 — 동그라미만 준다 — 기준 길이 max(높이, 폭 ÷ 4, 24) 에서 2px(Feedback 의 눌림 피드백, v104)motion-duration-pressed-scale 150msmotion-ease-pressed-scale cubic-bezier(0, 0, 0.15, 1)color-static-white #FFFFFFcolor-fg-neutral #1A1F2E · 다크 #F5F6FA———
disabled 전용 색(v106) — 불투명도로 흐리게 하지 않는다not-allowed — 포인터 이벤트는 켜 둔다 — 끄면 이 커서가 안 보인다(SEED 와 같다)color-bg-disabled #F5F6FA · 다크 #353B4D — Checkbox 와 같게 — 켜진 모양 그대로 색만(사용자 결정). SEED 는 채움을 빼고 테두리 + 커진 점0px———color-fg-disabled #8A91A0 · 다크 #838997color-fg-disabled #8A91A0 · 다크 #838997———
반복 — neutral
반복 — brand · 막힌 선택지
직접 눌러 보기 — 동그라미나 라벨을 누르면 바뀐다(Tab 으로 들어가 화살표로 옮긴다)

모션

전환시간곡선대상비고
선택 안 됨 ↔ 선택motion-duration-color-transition 150msmotion-ease-easing cubic-bezier(0.35, 0, 0.35, 1)background, border, dot color채움 · 테두리 · 점이 색으로 함께 바뀐다(SEED Radiomark 와 같다) — 점은 커지거나 줄지 않는다
누름motion-duration-pressed-scale 150msmotion-ease-pressed-scale cubic-bezier(0, 0, 0.15, 1)scale동그라미만 세로 2px 거리 축소 — 라벨은 줄지 않는다(v104)

Group

선택지를 묶어 세로로 쌓는다. 하나를 고르면 앞에 고른 것은 풀린다. 줄 사이는 12 다 — 줄 높이 32 · 36 에 더하면 44 · 48 마다 한 줄이 서서, 묶음 안에서도 줄마다 누르는 영역 44 를 온전히 받는다(사용자 결정). 줄은 동그라미 + 라벨만큼만 차지하고 묶음 폭으로 늘이지 않는다. 가로로 늘어놓지 않는다(사용자 결정) — 짧은 선택지를 한 줄에서 고르게 하려면 Segmented · Chip 을 쓴다.

반복
묶음 — 하나를 누르면 앞에 고른 것이 풀린다

묶음

속성값비고
간격spacing-x3 12px묶음 안 줄 사이(세로) — 줄 32 · 36 에 12 를 더해 44 · 48 마다 한 줄이라 이웃 줄과 누르는 영역(44)이 겹치지 않는다(사용자 결정 — SEED 는 4). 가로 배치는 두지 않는다

Guidelines

오늘 제품에는 라벨만 있는 Radio 가 없다 — 라디오 동그라미가 나오는 곳은 반복 거래의 "종료" 하나뿐이고, 설명과 입력칸이 붙어 있어 Select Box 로 옮긴다(아래 "설명 · 딸린 입력"). 이 절의 그림은 캘린더 일정의 반복 선택지(반복 없음 · 매일 · 매주 · 매월 · 매년)를 빌려 Radio 로 그렸다 — 제품의 그 자리는 지금 토글 묶음이다.

누르는 영역

라벨을 포함한 줄 전체가 누르는 영역이다. 동그라미 20 · 24 만으로는 작다 — 라벨까지 묶고 위아래로 44 까지 넓힌다. 묶음에서는 줄 사이 12 가 이 영역이 서로 겹치지 않게 한다.

medium줄마다 높이 44 — 이웃 줄과 겹치지 않는다
large줄마다 높이 44 — 이웃 줄과 겹치지 않는다
누르는 영역(분홍) — 동그라미 + 라벨이 한 영역

묶음 쓰기

묶음 위에 무엇을 고르는지 제목을 두고, 선택지는 세로로 쌓는다. 선택지 개수는 Select 의 "고르는 컴포넌트 고르기" 판단 순서를 따른다 — 2 ~ 4개 · 글이 길 때 Radio, 5개 이상은 Select. 처음부터 하나를 골라 둘 수 있으면 골라 둔다(보통 가장 흔한 것).

반복
이렇게 — 제목을 위에 두고 선택지를 세로로 — 처음 값(반복 없음)을 골라 둔다
반복
이렇게 하지 않는다 — 가로로 늘어놓기 — 줄이 바뀌면 어느 라벨이 어느 동그라미인지 흐려진다. 한 줄에서 고르려면 Segmented
제목 + 세로 묶음 · 가로로 늘어놓지 않는다

선택 색

선택 색은 짙은 회색이 기본이다. 브랜드 색은 서비스 핵심 흐름에만 — 선택 컨트롤마다 브랜드 색을 깔면 브랜드 색 버튼이 설 자리가 없어진다.

9:41
일정
반복
이렇게 — 선택은 짙은 회색 — 화면의 주 액션(저장)이 먼저 읽힌다
9:41
일정
반복
이렇게 하지 않는다 — 선택마다 브랜드 색을 깔면 브랜드 색이 흩어진다
선택 색

설명 · 딸린 입력

선택지마다 설명이 붙거나, 고르면 입력칸이 따라 나와야 하면 Radio 가 아니라 Select Box 다(사용자 결정 — SEED 와 같다). Radio 에는 라벨 하나만 있다(길면 줄이 바뀐다) — 설명 줄이나 딸린 입력 자리가 없다. 줄 안에 입력칸을 넣으면 화면 읽기 프로그램이 입력칸을 라디오의 일부로 읽는다.

종료
무기한중지할 때까지 계속 반복
횟수 지정정한 횟수만큼 반복
총회
종료일 지정정한 날까지 반복
이렇게 — Select Box — 설명은 제목 아래, 입력칸은 고른 상자 아래로 펼쳐진다
종료
중지할 때까지 계속 반복
총 12 회
2027년 3월 31일 (수)
이렇게 하지 않는다 — Radio 줄 안에 설명 · 입력칸 — 입력칸이 라디오의 일부로 읽히고, 누르는 영역이 겹친다
반복 거래 "종료" — 설명 · 입력칸이 붙는 선택은 Select Box

오류

동그라미 모양은 바꾸지 않는다. 묶음 아래에 무엇을 해야 하는지 글로 알린다(Checkbox 와 같다). 묶음을 Field 로 감싸면 그 꼬리가 오류 글 자리이고, 라벨은 묶음의 이름으로 이어진다(2026-10-01). Field 의 오류 · 필수 · 막힘도 묶음에 닿는다 — 묶음(role="radiogroup")에 aria-invalid · aria-required, 막히면 묶음 aria-disabled 와 선택지마다 disabled 다(2026-10-08, SEED 와 같다). 처음부터 하나를 골라 두면 오류가 날 일이 없다 — 골라 둘 수 없는 선택(사용자가 꼭 스스로 골라야 하는)에만 쓴다.

반복
반복을 골라 주세요.
이렇게 — 동그라미는 그대로, 묶음 아래 글로 무엇을 할지 알린다
반복
반복을 골라 주세요.
이렇게 하지 않는다 — 동그라미마다 빨간 테두리 — 모든 선택지가 틀린 것처럼 보인다
오류는 묶음 아래 글로

Radio 와 다른 선택 컨트롤

어느 컴포넌트로 고를지 — 선택지 개수 · 설명 · 글 길이로 고르는 표는 Select 의 "고르는 컴포넌트 고르기" 한 곳이다(2026-10-22 사용자 결정 1B · 2B — https://claude.ai/artifact/SmHD31RQxTjWbfK83nbcym). 여기에는 비슷한 셋을 견주는 표만 둔다(SEED Radio 문서의 표 + Desk 예).

RadioCheckboxChip
사용자의 인식하나의 정답을 고른다해당되는 것에 모두 체크한다특징을 고르거나 분류한다
고르는 수하나여럿하나 · 여럿 다(칩 묶음이 정한다)
글문장도 된다 — 줄이 바뀐다문장도 된다명사 하나 — 줄바꿈하지 않는다
놓는 모양세로만(사용자 결정)세로 · 부모가 맨 위가로 — 줄바꿈 또는 한 줄 스크롤
주 쓰임폼 값 제출포함 · 제외 · 동의폼 값(2 ~ 4) · 거르기(필터 바) · 제안
Desk 예공개 범위내보낼 데이터 · 약관 동의거래 종류 · 카테고리 필터 · 빠른 금액

설명 · 딸린 입력이 붙으면 Select Box, 화면 폭 목록에서 누르는 순간 바뀌는 고르기는 List 의 라디오 줄(ListRadioItem)이다.

부위설명
여러 개Checkbox
켜고 끄기 하나, 바로 적용Switch

코드

레시피 recipes/shadcn/components/ui/radio-group.tsx 를 쓴다 — RadioGroup(묶음) · Radio(동그라미 + 라벨) · Radiomark(동그라미). Radio · Radiomark 는 RadioGroup 안에서만 쓴다. 아래 미리보기는 스펙 값으로 그린 모습이다.

기본

기본 — medium · neutral
import { Radio, RadioGroup } from "@/components/ui/radio-group"

<RadioGroup defaultValue="none" aria-label="반복">
  <Radio value="none" label="반복 없음" />
  <Radio value="daily" label="매일" />
  <Radio value="weekly" label="매주" />
</RadioGroup>

크기 · 굵기

크기 · 굵기
<RadioGroup defaultValue="medium" aria-label="크기">
  <Radio value="medium" size="medium" label="medium" />
  <Radio value="large" size="large" label="large" />
  <Radio value="bold" size="large" weight="bold" label="large · bold" />
</RadioGroup>

톤

톤
<RadioGroup defaultValue="monthly" aria-label="반복">
  <Radio value="monthly" label="매월" />
  <Radio value="yearly" label="매년" />
</RadioGroup>

<RadioGroup defaultValue="monthly" aria-label="반복">
  <Radio value="monthly" tone="brand" label="매월" />
  <Radio value="yearly" tone="brand" label="매년" />
</RadioGroup>

한 묶음 안에서 톤을 섞지 않는다.

값 다루기 · 비활성

값 다루기 · 비활성
const [repeat, setRepeat] = useState("monthly")

<RadioGroup value={repeat} onValueChange={setRepeat} aria-labelledby="repeat-title">
  <Radio value="none" label="반복 없음" />
  <Radio value="monthly" label="매월" />
  <Radio value="yearly" label="매년" disabled />
</RadioGroup>

// 묶음에 disabled 를 주면 모든 선택지가 막힌다 — 고른 선택지는 채운 원 그대로 색만 바뀐다
<RadioGroup value={repeat} onValueChange={setRepeat} aria-labelledby="repeat-title" disabled>
  <Radio value="none" label="반복 없음" />
  <Radio value="monthly" label="매월" />
  <Radio value="yearly" label="매년" />
</RadioGroup>

오류

묶음을 Field 로 감싼다 — 라벨이 묶음의 이름, 오류 글이 묶음의 설명(aria-describedby)이 되고, 묶음에 aria-invalid 가 걸린다. 동그라미는 바꾸지 않는다.

반복
반복을 골라 주세요.
오류 — 묶음 아래 글
<Field label="반복" invalid errorMessage="반복을 골라 주세요.">
  <RadioGroup>
    <Radio value="none" label="반복 없음" />
    <Radio value="monthly" label="매월" />
  </RadioGroup>
</Field>

동그라미만

화면 폭 목록의 줄은 List 의 ListRadioItem 을 쓴다 — 줄의 여백 · 글자 · 누름이 정해져 있다. 라벨을 따로 짜야 할 때만 Radiomark 를 쓰고, 줄을 <label> 로 감싸 줄 어디를 눌러도 고르게 한다. group/radio 를 달면 줄을 누르거나 올려도 동그라미가 누름 색 · 축소로 반응한다. 직접 짠 줄은 묶음 폭을 다 쓴다(Radio 줄만 내용만큼 차지한다).

import { Radiomark, RadioGroup } from "@/components/ui/radio-group"

<RadioGroup defaultValue="none" aria-label="반복">
  <label className="group/radio flex cursor-pointer items-center gap-x3 px-x6 py-x3">
    <span className="flex-1">반복 없음</span>
    <Radiomark value="none" />
  </label>
</RadioGroup>

Behavior

인터랙션동작
Click / Tap(동그라미 · 라벨)그 선택지를 고른다. 앞에 고른 것은 풀린다. 이미 고른 것을 다시 눌러도 풀리지 않는다. disabled 면 무시.
Keyboard Tab묶음에 들어가고 나간다 — 고른 선택지(없으면 첫 선택지)로 들어간다. 묶음 안에서는 화살표로 옮긴다.
Keyboard ↑ ↓ ← →이전 · 다음 선택지로 옮기며 고른다. 막힌 선택지는 건너뛴다.
Keyboard Home · End받지 않는다 — 초점도 고른 선택지도 그대로, 화면도 스크롤되지 않는다(PageUp · PageDown 도). 첫 · 끝 선택지로는 화살표로 간다 — SEED · 진짜 라디오 · WAI-ARIA APG Radio Group 과 같다(2026-10-17 사용자 결정 — 03-E 결정 3 A). 라디오를 담는 묶음(Chip 하나 고르기 · Segmented Control · Select Box 하나 고르기 · List 라디오 줄)이 모두 같다. Tabs 는 Home · End 로 옮기며 고른다.
Keyboard Space포커스된 선택지를 고른다.
Keyboard Enter폼을 제출한다 — 진짜 <input type="radio"> 처럼 폼의 기본 버튼(첫 제출 버튼)을 누른다. 고르지 않는다. 기본 버튼이 없거나 막혀 있으면 아무것도 하지 않는다. Radix 의 선택지는 Enter 를 막기만 해서 묶음이 받는다(사용자 결정 2026-10-08 — SEED 와 같다). 동그라미만(Radiomark) 쓰는 List 의 라디오 줄 · Select Box 하나 고르기도 같다.
Disabled(묶음)모든 선택지가 막힌다 — 묶음 aria-disabled. 막힌 Field 로 감싸도 같다.
Disabled(선택지)그 선택지만 막힌다 — 골라 둔 채로 막을 수 있다(채운 원 그대로 색만).

Form 안 — Radix 의 숨은 <input type="radio"> 가 폼 제출에 들어간다(name 을 묶음에). 검증은 onSubmit — 누르는 동안 오류를 띄우지 않는다.

Accessibility

기준검증
WCAG 1.4.3 Color contrast(라벨 ≥ 4.5:1)라벨 fg-neutral × 흰 표면 16:1+ ✓
WCAG 1.4.11 Non-text contrast(UI ≥ 3:1)선택 안 된 동그라미 테두리 stroke-neutral-solid × 표면 4.2:1(다크 4.1:1) ✓ · 선택 채움 bg-neutral-inverted × 표면 16.4:1(다크 13.4:1) ✓ · 가운데 점 × 채움 — neutral 16.4:1(다크 13.4:1), brand Desk 8.4:1 · HR 8.2:1 ✓. 다크의 brand 채움은 표면과 1.7:1(Desk · HR)이라 채움만으로는 3:1 이 안 된다 — 선택은 가운데 흰 점(표면과 14.5:1)과 테두리가 사라지는 모양이 알린다
WCAG 2.4.7 Focus visible키보드 포커스에만 링 2px · 띄움 2px(stroke-focus-ring) — 앱도 키보드로 옮길 때만 같은 링(2026-10-08)
WCAG 2.5.8 Target Size — Minimum(AA ≥ 24×24)동그라미 20 · 24 + 라벨까지 묶은 줄 32 · 36 ✓
WCAG 2.5.5 Target Size — Enhanced(AAA ≥ 44×44)라벨까지 묶어 위아래 44 로 넓힌다 ✓. 묶음 안에서도 줄 사이 12 라 줄마다 44 · 48 을 받는다(줄 사이가 4 면 이웃과 겹쳐 36 · 40 이 된다) — 동그라미만 쓰면 그 줄 전체
ARIARadix 가 role="radiogroup"(묶음) + role="radio" · aria-checked(선택지)를 단다. 묶음 제목은 aria-labelledby(또는 <fieldset> + <legend>), 오류 글은 묶음에 aria-describedby. 오류 · 필수는 묶음의 aria-invalid · aria-required, 막히면 묶음 aria-disabled + 선택지마다 disabled — Field 로 감싸면 레시피가 건다. 동그라미만 쓰면 <label> 로 감싸거나 aria-label 필수. 앱은 묶음에 이름 노드만 둔다 — 묶음 제목을 Semantics(label) 로 단 노드 하나, 묶음 역할(radiogroup)은 없다(Flutter 3.44 에 그 역할이 없다 — 2026-10-17 사용자 결정 03A-014 A).
Reduced motion모션 줄이기면 누름 축소를 빼고 색만 바꾼다(기초 Motion).

Do / Don't

✅ Do

  • 묶음 위에 무엇을 고르는지 제목을 둔다.
  • 선택지는 세로로 쌓는다.
  • 골라 둘 수 있으면 처음부터 하나를 골라 둔다.
  • 하나만 고르기는 Radio, 여러 개 고르기는 Checkbox, 바로 적용되는 켜기 · 끄기는 Switch.
  • 선택지를 설명하는 링크는 라벨 옆이나 아래에 둔다 — 라벨은 고르는 글만.

❌ Don't

  • 선택지를 가로로 늘어놓기 — 짧은 선택지를 한 줄에서 고르게 하려면 Segmented · Chip.
  • 라디오 줄 안에 입력칸 · 버튼 넣기 — 설명 · 딸린 입력이 붙으면 Select Box.
  • 선택지 5개 이상 — Select 로 접는다(Select 의 판단 순서).
  • 선택 색을 브랜드로 깔아 두기 — 브랜드 색은 핵심 흐름에서만.
  • 오류를 동그라미 색만으로 알리기 — 묶음 아래 글로 알린다.
  • 라벨 안에 링크 · 버튼 — 줄의 누르는 영역(44)이 덮어 눌리지 않는다. 라벨 옆이나 아래에 따로 둔다(사용자 결정 2026-10-08).

Specification

radio-group.yaml 의 규칙을 하나도 빼지 않고 조건마다 그린다 — 웹 · 앱이 Radio 를 만들 때 이 값을 그대로 쓴다. 조건이 없는 Base 가 모든 조합에 걸리고, 뒤의 규칙이 앞의 같은 값을 덮는다. 상태는 enabled 에서 바뀌는 값만 적었다.

Base

부위상태속성값
rootenabled간격gapspacing-x28px
줄 맞춤alignSelfflex-start줄은 동그라미 + 라벨만큼만 차지한다 — 묶음 폭으로 늘이지 않는다(직접 짠 줄은 묶음 폭을 쓴다)
커서cursorpointer
터치 영역touchTarget44 × 44라벨까지 묶어 44 — 라벨이 따로 서는 자리에서 동그라미만 쓰면 그 줄 전체가 누르는 영역(SEED touch target · 기초 Inclusive)
radiomarkenabled모서리radiusradius-full9999px(알약)
전환 대상transitionPropertybackground-color, border-color
전환 시간transitionDurationmotion-duration-color-transition150ms
전환 곡선transitionEasingmotion-ease-easingcubic-bezier(0.35, 0, 0.35, 1)
dotenabled모서리radiusradius-full9999px(알약)
전환 대상transitionPropertybackground-color
전환 시간transitionDurationmotion-duration-color-transition150ms
전환 곡선transitionEasingmotion-ease-easingcubic-bezier(0.35, 0, 0.35, 1)
labelenabled글자·아이콘foregroundfg-neutral#1A1F2E다크 #F5F6FA
글꼴fontFamilyfont-sansPretendard, Inter, sans-serif
groupenabled간격gapspacing-x312px묶음 안 줄 사이(세로) — 줄 32 · 36 에 12 를 더해 44 · 48 마다 한 줄이라 이웃 줄과 누르는 영역(44)이 겹치지 않는다(사용자 결정 — SEED 는 4). 가로 배치는 두지 않는다
focusRingfocused링 두께width2px웹 · 앱 같다 — 앱은 하드웨어 키보드로 옮길 때만 그린다(Flutter FocusHighlightMode.traditional), 손가락으로 누를 때는 없다(2026-10-08 — DESIGN.md State · Button 과 같다)
링 색colorstroke-focus-ringDesk#0147AD다크 #7AA9F6HR#075B3C다크 #2ABE84
링 간격offset2px
radiomarkpressed배율scale세로 2px 축소동그라미만 준다 — 기준 길이 max(높이, 폭 ÷ 4, 24) 에서 2px(Feedback 의 눌림 피드백, v104)
축소 시간scaleDurationmotion-duration-pressed-scale150ms
축소 곡선scaleEasingmotion-ease-pressed-scalecubic-bezier(0, 0, 0.15, 1)
rootdisabled커서cursornot-allowed포인터 이벤트는 켜 둔다 — 끄면 이 커서가 안 보인다(SEED 와 같다)
labeldisabled글자·아이콘foregroundfg-disabled#8A91A0다크 #838997

size=medium

부위상태속성값
rootenabled최소 높이minHeight32px
radiomarkenabled크기size20px
dotenabled크기size8px
labelenabled글자typographytext-t414px / 19px

size=large

부위상태속성값
rootenabled최소 높이minHeight36px
radiomarkenabled크기size24px
dotenabled크기size10px
labelenabled글자typographytext-t516px / 22px

weight=regular

부위상태속성값
labelenabled굵기fontWeight400

weight=bold

부위상태속성값
labelenabled굵기fontWeight700

checked=unchecked

부위상태속성값
radiomarkenabled배경backgroundtransparent
테두리borderColorstroke-neutral-solid#767C8B다크 #8389973:1 이상 — v109(SEED 는 옅은 선)
테두리 두께borderWidth1px
dotenabled색colortransparent선택 안 됨에는 점이 보이지 않는다
radiomarkhovered배경backgroundbg-layer-default-pressed#F7F8FD다크 #353B4D
radiomarkpressed배경backgroundbg-layer-default-pressed#F7F8FD다크 #353B4DSEED bg.transparent-pressed 의 불투명 짝
radiomarkdisabled배경backgroundbg-disabled#F5F6FA다크 #353B4DSEED palette.gray-300 자리
테두리borderColorstroke-neutral-weak#E5E8EF다크 #404757SEED stroke.neutral-weak(gray-400)

checked=checked

부위상태속성값
radiomarkenabled테두리 두께borderWidth0px
radiomarkdisabled배경backgroundbg-disabled#F5F6FA다크 #353B4DCheckbox 와 같게 — 켜진 모양 그대로 색만(사용자 결정). SEED 는 채움을 빼고 테두리 + 커진 점
dotdisabled색colorfg-disabled#8A91A0다크 #838997

tone=neutral, checked=checked

부위상태속성값
radiomarkenabled배경backgroundbg-neutral-inverted#1A1F2E다크 #F5F6FA
dotenabled색colorfg-neutral-inverted#FFFFFF다크 #242938
radiomarkhovered배경backgroundbg-neutral-inverted-pressed#535866다크 #B7BDCC
radiomarkpressed배경backgroundbg-neutral-inverted-pressed#535866다크 #B7BDCC

tone=brand, checked=checked

부위상태속성값
radiomarkenabled배경backgroundbg-brand-solidDesk#0147AD다크 #1049A4HR#075B3C다크 #1B583E
dotenabled색colorstatic-white#FFFFFF
radiomarkhovered배경backgroundbg-brand-solid-pressedDesk#013D96다크 #1A5AC2HR#064F33다크 #0C6C49
radiomarkpressed배경backgroundbg-brand-solid-pressedDesk#013D96다크 #1A5AC2HR#064F33다크 #0C6C49

SEED 와 다른 점

  • 선택 안 된 동그라미의 테두리는 stroke-neutral-solid(3:1 이상, v109). SEED 는 옅은 선(stroke.neutral-weak)이지만 groo 는 동그라미를 선으로 알아봐야 한다(1.4.11).
  • 비활성은 Checkbox 와 같게(사용자 결정) — 선택 안 됨은 bg-disabled + stroke-neutral-weak, 선택은 채운 원 그대로 bg-disabled + fg-disabled 점. SEED 는 팔레트 gray-300 을 쓰고, 선택이면 채움을 빼고 테두리 + 커진 점(8 → 10)으로 그린다.
  • 투명한 누름(SEED bg.transparent-pressed)은 불투명한 가장 가까운 역할(bg-layer-default-pressed)로 — 8자리 hex 를 lint 가 거부한다.
  • 묶음은 세로만(사용자 결정) — SEED 는 가로를 피하라고 하되, 꼭 가로로 놓아야 하면 간격을 넉넉히 두라고 한다. groo 는 가로 자리에 Segmented · Chip 을 쓴다.
  • 묶음 줄 사이는 12(사용자 결정) — SEED 는 4 다. 4 면 이웃 줄의 누르는 영역(44)과 겹쳐 한 줄이 실제로 36 · 40 만 받는다. groo 는 기초에서 44 를 반드시로 정했다(Inclusive Design).
  • 웹의 hovered · focused 를 더한다(v106). focused 는 앱의 키보드 포커스에도 그린다(2026-10-08).

Migration notes

2026-10-22 — 선택지 개수는 Select 의 판단 순서 하나로

"둘에서 다섯 · 여섯 이상이면 Select" 는 옛 스펙(b22ec39^ — "2–5개 · 6+ Select")에서 이어진 숫자로 결정 기록이 없었다. Select 의 "고르는 컴포넌트 고르기"(10-01 결정 — 2 ~ 4 · 5 이상) 하나로 맞추고, "Radio 와 다른 선택 컨트롤" 표를 그 절 링크 + Radio · Checkbox · Chip 견주기 표(SEED)로 바꿨다(사용자 결정 1B · 2B — https://claude.ai/artifact/SmHD31RQxTjWbfK83nbcym). 사이트 그림 · 레시피 예제 · 미리보기의 반복 선택지 다섯(매년까지)을 넷으로 줄였다 — 다섯이면 이제 Select 자리다.

2026-10-17 — 하나 고르기 묶음은 Home · End 를 받지 않는다(03-E 결정 3 A)

웹 공용 라디오 묶음(Chip 하나 고르기 · Radio Group · Segmented Control · Select Box 하나 고르기 · List 라디오)은 Radix RovingFocusGroup 이 Home · End(· PageUp · PageDown)로 초점만 첫 · 끝으로 옮겨 초점과 고른 것이 갈렸다. 사용자가 비교 페이지에서 "받지 않는다"(A — 화살표만, SEED · 진짜 라디오 · APG)를 골랐다. 레시피는 묶음이 캡처 단계에서 라디오의 그 키를 막는다(ignoreRadioJumpKeys — radio-group.tsx, Radio Group 도 쓴다). 웹 ds 의 같은 묶음도 같게, 앱은 바뀌는 것 없음. 옮기며 고르는 안(B — Tabs 처럼)은 고르지 않았다. 옛 스펙은 radio-group.history/v-pre-03e-03b.md.

2026-10-08 — Enter · 묶음 상태 · 오류 예 · 라벨 안 링크

앱 적용(desk-front #428 ~ #430 · desk-app #407 ~ #410)이 남긴 문제를 사용자가 비교 페이지에서 정했다.

  • Enter(B1) — 스펙은 "Enter 는 폼 제출에 둔다" 인데 Radix 는 Enter 를 막기만 해 아무 일도 없었다. Radix 의 선택지는 onKeyDown 을 덮어쓰므로 묶음이 Enter 를 받아 폼의 기본 버튼을 누른다 — 진짜 input · SEED 와 같다. 고르기는 Space · 화살표 그대로다. Radiomark 를 담는 List(ListRadioGroup) · Select Box(RadioSelectBoxGroup) 묶음도 같은 처리(submitOnRadioEnter)를 쓴다.
  • 묶음 상태(9) — Field 의 오류 · 필수 · 막힘이 묶음에 닿지 않았다(묶음 aria-required 는 늘 "false"). 묶음에 aria-invalid · aria-required · aria-disabled 를, 막히면 선택지마다 disabled 를 건다. 묶음에 직접 준 값이 이긴다.
  • 오류 예(9) — "코드" 절의 오류 예가 Field 전의 꼴(text-t2 글을 손으로 잇기)이었다. Field 판으로 바꿨다.
  • 라벨 안 링크(C1) — 줄의 누르는 영역이 라벨 안 링크를 덮는다. 레시피는 그대로 두고 Don't 에 적었다 — 링크는 라벨 옆 · 아래에.

2026-09-30 — SEED Radio 구조로

사용자가 비교 페이지(https://claude.ai/artifact/Y19vXuAFz7rjMtTukR3Xt4)에서 정했다 — 틀은 SEED(Radiomark · Radio · Radio Group, 크기 medium 20 · large 24 + 라벨 14 · 16 + 줄 32 · 36) · 세로만(가로 배치 없음) · 묶음 줄 사이 12(SEED 는 4 — 누르는 영역 44 를 지키려고 넓혔다, Checkbox 묶음도 같은 값으로) · 선택 모양은 SEED(채운 원 + 가운데 점 8 · 10) · 비활성 선택은 채운 원 + 회색 점(Checkbox 와 같게) · 설명 · 딸린 입력이 붙는 선택은 Select Box. 선택 색(neutral 기본 + brand)과 오류(묶음 아래 글)는 Checkbox 때 정한 규칙이다.

옛새
크기 하나 18medium(20, 기본) · large(24)
선택 = border-primary 테두리 + 가운데 점 16(primary)선택 = 테두리 없이 채운 원(bg-neutral-inverted, tone="brand" 면 bg-brand-solid) + 가운데 점 8 · 10
선택 안 됨 테두리 border-strongstroke-neutral-solid(값은 같다 — 역할 이름으로) · 호버 = 누름 bg-layer-default-pressed
비활성 50% 흐림전용 색 — bg-disabled · stroke-neutral-weak · fg-disabled(v106)
묶음 줄 사이 8(gap-sm) · 짧은 둘은 가로(사이 16 · 24)줄 사이 12(spacing-x3) · 세로만
라벨은 쓰는 쪽이(Label 14/500 · gap-md) · 옵션마다 설명 줄(caption)Radio 가 라벨까지 — t4 14/400(굵게는 weight="bold" 700). 설명이 붙으면 Select Box
동그라미 컴포넌트 이름 RadioGroupItem동그라미는 Radiomark, Radio 는 동그라미 + 라벨
전환 motion-duration-fast · ease-outmotion-duration-color-transition · motion-ease-easing(v104 이름) + 누름 축소

제품은 앱 적용 단계에서 옮긴다 — 라디오 동그라미가 나오는 곳은 반복 거래의 "종료" 하나다(웹 추가 대화상자 · 거래에서 반복 만들기 대화상자, 앱 반복 설정). 셋 다 그 화면 안에서 손으로 그렸고(웹은 17 · 2px 고리 + 점 7, 앱은 17 · 채운 원 + 흰 점 6), 설명 · 입력칸이 붙어 Select Box(select-box.md)로 옮긴다. 앱의 공용 PRadio · PRadioTile 은 쓰는 곳이 없다(점이 14 로 옛 스펙 16 과도 달랐다). HR 웹에는 라디오가 없다.

이 페이지