A circular radio control used inside single-select groups; supports default, selected, and disabled.
Split properties + rebuild as vector
Replace the sparse
selected × style matrix with orthogonal props: selected: Bool + state: default/disabled/error. Retire the checkmark style (it's a checkbox affordance, not a radio). Rebuild the large radio with token-bound vector layers instead of raster SVG images. Rename .base/checkbox → .base/radio. Add pressed + focused states.In Context
Radio Buttons appear in Radio Button with Label groups — see the Radio Button with Label preview for the composed form row.
Live Preview
Properties
selected
size
style
DS Health
Reusable
Pass
Used in forms, surveys, preference pickers. Two sizes cover 360px and 414px screen needs.
Self-contained
Warn
Small variants use token-bound borders + fills. Large variants export a pre-rendered SVG image for each state — tokens won't propagate to the large size. C3
Consistent
Warn
Two property-naming issues:
selected mixes selection with modifiers (disabled/error), and style is conditional (only meaningful when selected is true). Sparse matrix with ~50% invalid combinations. C2Composable
Warn
Internal frame is named
.base/checkbox instead of .base/radio. Suggests checkbox primitives were reused here. Also the checkmark style is a checkbox affordance, not standard radio iconography. C6Open Issues
- Sparse variant matrix.
selected × size × style= 24 theoretical, ~11 valid. Thestyleproperty is only meaningful when a selection is present, andselectedconflates selection with modifier states. C2 · Variant & Property Naming - Large radio is raster-baked. Every large variant exports the ring+dot as a pre-rendered SVG image (
imgContainer). Token changes won't propagate to the large size until this is rebuilt with layered vectors. C3 · Token Coverage - Internal frame named
.base/checkbox. Misleading layer naming — the small radio nests a frame called.base/checkboxinstead of.base/radio. C6 · Asset & Icon Quality - Checkmark style is not standard radio iconography. Radios use filled dots universally; checkmarks communicate "checked" — a checkbox affordance. The
style=checkmarkvariant visually overlaps with Checkbox. C6 · Asset & Icon Quality - No pressed or focused states. Engineers must improvise these affordances. C5 · Interaction State Coverage
- Code Connect mappings not registered. Blocked until property split (selected/state/size) and large-radio vector rebuild land. C7 · Code Connect Linkability
Design Recommendations
- Split properties into orthogonal axes :
•selected: Bool(true/false) — pure selection state
•state: default / disabled / error— modifier state (can combine with selected)
•size: small / large— unchanged
Eliminates invalid combinations, maps to SwiftBooland native radio APIs. Property - Retire the checkmark style. Pick filled (blue dot) as the single visual style — it's the universally understood radio affordance. The checkmark variant is visually a checkbox and may cause user confusion when placed next to actual checkboxes. Rename
- Rebuild the large radio as vector layers. Each variant today exports a flat SVG image; convert to a base ring + inner dot, both with token-bound fills. Matches the small radio's structure and lets tokens flow to both sizes. Asset
- Rename internal frame
.base/checkbox→.base/radio. Minor but signals correct primitive ownership. Rename - Add pressed + focused states. Pressed = darker blue ring/fill; focused = outer 2px focus ring. Documents the interactive affordances native needs to render. State
Styles
All states — large + small
DES DEV
Pick a state, size, and style to preview. The same matrix would otherwise be 12 static cells; here it's controlled.
Properties
selected
size
style
Properties
Variant All states — large + small
Selected true/false (boolean)
Colors
Border #D7E0EF
Fill #005CE5
Dot #FFFFFF
Error border #D61B2C
Layout
Outer ring 20 × 20
Inner dot 10 × 10 (when selected)
Border radius 50% (circle)
Border 1.5px solid
Typography
Inline label Primary/Multi-line Label/Light/Small
Label font Proxima Soft Semibold · 14 / 16
Colors by State
| Role | Token | Token | Value |
|---|---|---|---|
| Unselected | border | main/radio-button/color/default/unselected/border | #D7E0EF |
| Selected | bg (fill + ring) | main/radio-button/color/default/selected/bg | #005CE5 |
| — | border | main/radio-button/color/default/selected/border | #005CE5 |
| — | inner dot / checkmark | main/radio-button/color/default/selected/icon | #FFFFFF |
| Disabled | bg | main/radio-button/color/disabled/selected/bg | #C2CFE5 |
| — | border | main/radio-button/color/disabled/selected/border | #C2CFE5 |
| — | inner icon | main/radio-button/color/disabled/selected/icon | #FFFFFF |
| Error | border (unselected) | main/radio-button/color/error/unselected/border | #D61B2C |
| — | bg (selected) | main/radio-button/color/error/selected/bg | #D61B2C |
| — | border (selected) | main/radio-button/color/error/selected/border | #D61B2C |
Installation
Planned API
iOS — Swift Package Manager
// In Xcode: File → Add Package Dependencies "https://github.com/AY-Org/eb-ds-ios"
Android — Gradle (Kotlin DSL)
dependencies { implementation("com.eastblue.ds:radio:1.0.0") }
Property Mapping
| Figma Property | SwiftUI | Compose |
|---|---|---|
| selected=unselected/selected | selected: Bool | selected: Bool |
| selected=disabled | state=disabled (combine w/ selected) | .disabled(true) |
| selected=error | state=error | state: .error |
| size=small/large | size: EBRadioSize | .controlSize(.small) |
| style=filled/checkmark | (retire — pick filled only) | — |
| style=default (unselected/error) | implicit from selected=false | — |
| — | onToggle | onChange: (Bool) -> Void |
SwiftUI
ios/Components/Radio/EBRadioButton.swift
Jetpack Compose
android/components/radio/EBRadioButton.kt
Usage Snippets Planned API
Usage
// Basic selection EBRadioButton(selected: $isSelected) // Large size with error state EBRadioButton(selected: $isSelected, state: .error) .controlSize(.large) // Disabled EBRadioButton(selected: true) .disabled(true)
// Basic selection EBRadioButton(selected = checked, onCheckedChange = { checked = it }) // Large size with error state EBRadioButton( selected = checked, onCheckedChange = { checked = it }, size = EBRadioSize.Large, state = EBRadioState.Error ) // Disabled EBRadioButton( selected = true, onCheckedChange = { }, enabled = false )
Accessibility
| Requirement | iOS | Android |
|---|---|---|
| Role | Inherit radio semantics via Toggle(isOn:) with radio style | Use Modifier.selectable(role = Role.RadioButton) |
| Selected state | .accessibilityAddTraits(.isSelected) | selected = true in semantics |
| Group label | Wrap options in a .accessibilityElement(children: .contain) with group label | Use Modifier.selectableGroup() on parent |
| Tap target | Radio is 20px; wrap in 44pt hit area | Wrap in 48dp hit area |
| Error announcement | Pair with a label and announce the error message after the label | Use semantics { error(...) } |
Criteria Scorecard
| ID | Criterion | Status | Notes |
|---|---|---|---|
| C1 | Layer Structure & Naming | Needs Refinement | Internal frame named .base/checkbox instead of .base/radio. |
| C2 | Variant & Property Naming | Requires Rework | selected mixes selection with modifiers; style is conditional. |
| C3 | Token Coverage | Requires Rework | Large variants are raster — tokens don't flow to the large size. |
| C4 | Native Mappability | Ready | Maps to Toggle / RadioButton with custom style. |
| C5 | Interaction State Coverage | Requires Rework | No pressed or focused states. |
| C6 | Asset & Icon Quality | Requires Rework | Checkmark style conflicts with Checkbox visually; large radio is a pre-rendered image. |
| C7 | Code Connect Linkability | Needs Refinement | Blocked by C2. Clean mapping lands after prop split. |
Variants Inventory (11 total)
After the proposed restructure: 2 selected × 3 state × 2 size = 12 well-formed orthogonal variants (no invalid combinations possible).
| selected | size | style | Node ID |
|---|---|---|---|
| unselected | large | default | 18482:35699 |
| unselected | small | default | 18482:35702 |
| selected | large | filled | 18482:35715 |
| selected | small | filled | 18482:35718 |
| selected | large | checkmark | 18482:35721 |
| selected | small | checkmark | 18482:35724 |
| disabled | large | filled | 18482:35704 |
| disabled | small | filled | 18482:35707 |
| disabled | large | checkmark | 18482:35710 |
| disabled | small | checkmark | 18482:35713 |
| error | large | default | 18482:35726 |
1.0.0 — April 2026Major
Initial Assessment · node 18482:35698
Component assessed — 11 variants across sparse selected × size × style matrix. Documented
InitialSparse matrix, mixed paradigms —
C2 Openselected mixes selection with modifier states; style is only meaningful when selected. OpenLarge radio is raster-baked — tokens don't flow to the large size. Open
C3 OpenInternal frame named
C6 Open.base/checkbox + checkmark style overlaps with Checkbox. OpenNo pressed/focused states. Open
C5 OpenCode Connect mappings — Not registered. Open
C7 Open