Components · Forms & selection
Radio
Mutually exclusive selection control. Split from zetta-form-v1_4.md (removed in v1.5) in v1.4.
Preview
Build with the skill
No package to install — hand this to your AI to generate or migrate the component in your stack.
Apply the Zetta "Radio" component to this project using the zetta-design-md skill.
WHEN TO USE
- Migrating or fixing existing code (the common case): audit the current implementation against the `radio` spec and CORRECT every deviation — replace off-spec values, don't only add new ones.
- New component: build it to the `radio` spec in this project's stack.
AUDIT & FIX (when the component already exists)
Compare it to the skill's spec and fix, don't just append: hardcoded colors/hex, off-scale radius, wrong fonts, dark/navy fills, shadows used in place of borders (or missing borders), brand lime used as a CTA or interaction (must be primary indigo), and any missing or wrong hover / focus / active / disabled / invalid state or missing 2px focus ring. Replace each with the correct Zetta token.
ALWAYS
- Pull the authoritative spec and tokens from the skill — never guess or hardcode values.
- Use this project's own component conventions and framework idioms.
- Honor the Zetta guardrails (brand = identity only, primary = interaction, shadows for overlays) and verify in Light, Dark, and Accessibility. Anatomy & rules
◉ Option A ← selected: {colors.primary} border + 10px indicator dot
○ Option B ← unselected: {colors.border-strong} border
○ Option C
| State | Border | Indicator |
|---|---|---|
| Default (unselected) | {colors.border-strong} |
None |
| Hover | {colors.border-strong} |
None |
| Focus | {colors.border-strong} + 2px solid {colors.border-focus} outline |
None |
| Selected | {colors.primary} |
{colors.primary} 10px dot |
| Disabled (unselected) | {colors.disabled-border} |
None |
| Disabled (selected) | {colors.disabled-border} |
{colors.disabled-text} dot |
When to use
- Selecting one option from a small set (2–6 options).
- When all options should be visible simultaneously for comparison.
When not to use
- More than 6 options — use Select.
- Multiple selections allowed — use Checkbox.
- On/off toggle — use Toggle Switch.
Do
- Always group related radios under a
<fieldset>+<legend>. - Pre-select the most common or safest option as the default.
- List options in a logical order: most common first, or alphabetical.
Don't
- Don't use radio for actions — use buttons.
- Don't allow a radio group with no option selected after initial render (always have a default).
- Option labels: noun or noun phrase. Sentence case. No trailing punctuation.
- Group legend: noun describing what's being chosen. "Billing cycle", "Notification frequency".
- Keep option labels parallel in structure: all nouns, or all adjectives — don't mix.
Keyboard
| Key | Behavior |
|---|---|
Tab |
Move focus into the radio group |
Arrow Up / Left |
Select previous option |
Arrow Down / Right |
Select next option |
Space |
Select focused option (if not already selected) |
Accessibility
- ARIA role: implicit
role="radio"on<input type="radio">. - Group all options in
<fieldset>+<legend>— the legend is announced before each option. nameattribute groups radios — required for mutually exclusive behavior.aria-disabled="true"on disabled radios (in addition todisabledattribute).- Hit area: 12px padding expands touch target toward 44×44px minimum.
Spec
Values and token references resolved straight from the Zetta spec.
radio
- backgroundColor
- transparent
- borderColor
- {colors.border-strong}
- borderWidth
- 2px
- borderRadius
- {rounded.full}
- size
- 20px
- cursor
- pointer
- padding
- 12px
radio-hover
- borderColor
- {colors.border-strong}
radio-focus
- outline
- 2px solid {colors.border-focus}
- outlineOffset
- 2px
radio-checked
- borderColor
- {colors.primary}
- indicatorColor
- {colors.primary}
- indicatorSize
- 10px
radio-disabled
- borderColor
- {colors.disabled-border}
- cursor
- not-allowed
radio-checked-disabled
- borderColor
- {colors.disabled-border}
- indicatorColor
- {colors.disabled-text}