A form row pairing a Radio Button with a text label. 4 variants across a size property with mixed values: default, large, default - error, large - error. Only the unselected state is documented across sizes.
size property into size=default/large and isError: Bool. Add disabled and selected variants. Instance-swap (or Figma Slot) the radio so the large label pairs with a large radio. The label component should track the atom's state via a single selected prop forwarded down.Contexts are illustrative. Final screens will reference actual GCash patterns. Radio Button with Label appears in form questions and preference settings, stacked vertically as a group.
size values include "default - error" and "large - error" — space-hyphen-space strings that encode state in a size prop. Breaks native enum mapping. C2size=large. The large label doesn't visually scale the radio accordingly. C6-
sizeproperty encodes state. Values include"default - error"and"large - error". Should be two orthogonal props:size = default | large+isError: Bool. C2 · Variant & Property Naming - Missing state variants. No
disabledorselectedvariants. Forms need all four selection states (selected/unselected × enabled/disabled) plus the error variant. C5 · Interaction State Coverage - Radio is hardcoded to the small instance. Even in
size=largevariants, the radio uses the 16×16 small atom. Large label should pair with the 20×20 large radio. C6 · Asset & Icon Quality - Code Connect mappings not registered. Blocked until the
sizeprop split and missing state variants land. C7 · Code Connect Linkability
- Split the
sizeprop :
•size: default / large
•isError: Bool
•selected: Bool
•disabled: Bool(or unifiedstateenum)
Flat orthogonal props — eliminates the compound string values. Property - Pair radio size to label size —
size=default→ small radio (16 × 16);size=large→ large radio (20 × 20). The current always-small behavior breaks visual hierarchy. Composition - Adopt a Figma Slot for the radio — lets consumers swap in a Radio Button with any state (selected/disabled/error/etc.) from the atom component. Maps to
@ViewBuilder/@Composableslots. Slot - Add disabled + selected variants — forms need all state combinations documented. State
- Make the whole row tappable — labels should be tap targets, not just the radio. Document in Accessibility. A11y
Small radio + 14 / 16 label. Default unselected state.
| Role | Token | Spec |
|---|---|---|
| default | Primary/Multi-line Label/Light/Small | Proxima Soft Semibold · 14 / 16 · +0.25 |
| large | Primary/Multi-line Label/Light/Base | Proxima Soft Semibold · 16 / 20 · +0.25 |
16 / 20 label (Proxima Soft Semibold). Still uses the 16 × 16 radio — should be 20 × 20.
| Role | Token | Value |
|---|---|---|
| Radio → label gap | space/space-12 | 12px |
| Radio icon offset padding (top) | space/space-4 | 4px |
| Text container padding (default) | space/space-4 | 4px vertical |
| Text container padding (large) | — | 3t / 5b |
| Default width (demo instance) | — | 63px |
| Large width (demo instance) | — | 69px |
Default size with red radio border. Label text color unchanged.
Large size with red radio border.
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") }
| Figma Property | SwiftUI | Compose |
|---|---|---|
| size=default / large | size: EBRadioSize | .controlSize(.small/.large) |
| "- error" suffix | isError: Bool | .ebError(true) |
| — | selected: Bool | selected: Bool |
| — | label: String | title: String |
| — | disabled: Bool | .disabled(true) |
| — | onToggle | onChange: (Bool) -> Void |
// Default row EBRadioButtonWithLabel("Pay with GCash", selected: $selection == .gcash) { selection = .gcash } // Large row with error EBRadioButtonWithLabel("Option A", selected: isSelected) .controlSize(.large) .ebError(true) // Disabled EBRadioButtonWithLabel("Not available", selected: false) .disabled(true)
// Default row EBRadioButtonWithLabel( label = "Pay with GCash", selected = selection == Option.Gcash, onCheckedChange = { selection = Option.Gcash } ) // Large row with error EBRadioButtonWithLabel( label = "Option A", selected = isSelected, onCheckedChange = { isSelected = it }, size = EBRadioSize.Large, isError = true ) // Disabled EBRadioButtonWithLabel( label = "Not available", selected = false, onCheckedChange = { }, enabled = false )
| Requirement | iOS | Android |
|---|---|---|
| Whole-row tap target | Wrap the row in a Button or tap gesture — label must be tappable, not just the radio | Apply Modifier.selectable(...) to the row |
| Group semantics | Wrap options in .accessibilityElement(children: .contain) | Use Modifier.selectableGroup() on parent |
| Role announcement | Radio Button role inherited from the atom | Role.RadioButton via selectable |
| Error announcement | Include error state in the option's accessibility label | Use semantics { error(...) } |
| Touch target size | Row should be at least 44pt tall | 48dp minimum |
| ID | Criterion | Status | Notes |
|---|---|---|---|
| C1 | Layer Structure & Naming | Ready | icon-ofsset (typo — "offset"), text-container. Minor spelling issue. |
| C2 | Variant & Property Naming | Requires Rework | size values encode error state with space-hyphen-space strings. |
| C3 | Token Coverage | Ready | Label colors, gap, padding all token-bound. |
| C4 | Native Mappability | Ready | HStack / Row with radio + label. |
| C5 | Interaction State Coverage | Requires Rework | No disabled, selected, or pressed variants. |
| C6 | Asset & Icon Quality | Needs Refinement | Always uses small radio instance — large label doesn't scale the radio. |
| C7 | Code Connect Linkability | Needs Refinement | Blocked by C2 prop split. |
After the prop split, these 4 become 2 size × 2 isError = 4 clean variants, with selected + disabled added as booleans (not variants).
| size (current) | Decomposed | Node ID |
|---|---|---|
default | size=default, isError=false | 18482:35674 |
large | size=large, isError=false | 18482:35686 |
default - error | size=default, isError=true | 18482:35680 |
large - error | size=large, isError=true | 18482:35692 |
size prop encodes state — "default - error" and "large - error" mix size + state. Should split. Open