Radio group
Stable Available in release assets
Group mutually exclusive choices as native radio inputs inside a fieldset and legend so browsers own selection and keyboard behavior.
Radio group primary example
<fieldset class="thin-fieldset">
<legend>Contact preference</legend>
<div class="thin-choice-row">
<input class="thin-choice" id="contact-email" type="radio" name="contact" value="email" checked>
<label for="contact-email">Email</label>
</div>
<div class="thin-choice-row">
<input class="thin-choice" id="contact-text" type="radio" name="contact" value="text">
<label for="contact-text">Text message</label>
</div>
<div class="thin-choice-row">
<input class="thin-choice" id="contact-none" type="radio" name="contact" value="none">
<label for="contact-none">Do not contact me</label>
</div>
</fieldset>Show code
<fieldset class="thin-fieldset">
<legend>Contact preference</legend>
<div class="thin-choice-row">
<input class="thin-choice" id="contact-email" type="radio" name="contact" value="email" checked>
<label for="contact-email">Email</label>
</div>
<div class="thin-choice-row">
<input class="thin-choice" id="contact-text" type="radio" name="contact" value="text">
<label for="contact-text">Text message</label>
</div>
<div class="thin-choice-row">
<input class="thin-choice" id="contact-none" type="radio" name="contact" value="none">
<label for="contact-none">Do not contact me</label>
</div>
</fieldset>Variant gallery
Each authored example uses the production assets and includes copyable markup.
Vertical radio group
A fieldset legend names the group; each option has its own label.
<fieldset class="thin-fieldset">
<legend>Contact preference</legend>
<div class="thin-choice-row">
<input class="thin-choice" id="contact-email" type="radio" name="contact" value="email" checked>
<label for="contact-email">Email</label>
</div>
<div class="thin-choice-row">
<input class="thin-choice" id="contact-text" type="radio" name="contact" value="text">
<label for="contact-text">Text message</label>
</div>
<div class="thin-choice-row">
<input class="thin-choice" id="contact-none" type="radio" name="contact" value="none">
<label for="contact-none">Do not contact me</label>
</div>
</fieldset>Show code
<fieldset class="thin-fieldset">
<legend>Contact preference</legend>
<div class="thin-choice-row">
<input class="thin-choice" id="contact-email" type="radio" name="contact" value="email" checked>
<label for="contact-email">Email</label>
</div>
<div class="thin-choice-row">
<input class="thin-choice" id="contact-text" type="radio" name="contact" value="text">
<label for="contact-text">Text message</label>
</div>
<div class="thin-choice-row">
<input class="thin-choice" id="contact-none" type="radio" name="contact" value="none">
<label for="contact-none">Do not contact me</label>
</div>
</fieldset>Wrapped radio row
Options can sit inside a cluster when space allows intrinsic wrapping.
<fieldset class="thin-fieldset">
<legend>Size</legend>
<div class="thin-cluster">
<div class="thin-choice-row">
<input class="thin-choice" id="size-s" type="radio" name="size" value="s" checked>
<label for="size-s">Small</label>
</div>
<div class="thin-choice-row">
<input class="thin-choice" id="size-m" type="radio" name="size" value="m">
<label for="size-m">Medium</label>
</div>
<div class="thin-choice-row">
<input class="thin-choice" id="size-l" type="radio" name="size" value="l">
<label for="size-l">Large</label>
</div>
</div>
</fieldset>Show code
<fieldset class="thin-fieldset">
<legend>Size</legend>
<div class="thin-cluster">
<div class="thin-choice-row">
<input class="thin-choice" id="size-s" type="radio" name="size" value="s" checked>
<label for="size-s">Small</label>
</div>
<div class="thin-choice-row">
<input class="thin-choice" id="size-m" type="radio" name="size" value="m">
<label for="size-m">Medium</label>
</div>
<div class="thin-choice-row">
<input class="thin-choice" id="size-l" type="radio" name="size" value="l">
<label for="size-l">Large</label>
</div>
</div>
</fieldset>Required group validation
Native required validation applies to the shared-name radio group.
<form>
<fieldset class="thin-fieldset">
<legend>Delivery speed</legend>
<div class="thin-choice-row">
<input class="thin-choice" id="speed-standard" type="radio" name="speed" value="standard" required>
<label for="speed-standard">Standard</label>
</div>
<div class="thin-choice-row">
<input class="thin-choice" id="speed-express" type="radio" name="speed" value="express">
<label for="speed-express">Express</label>
</div>
</fieldset>
<button type="submit">Continue</button>
</form>Show code
<form>
<fieldset class="thin-fieldset">
<legend>Delivery speed</legend>
<div class="thin-choice-row">
<input class="thin-choice" id="speed-standard" type="radio" name="speed" value="standard" required>
<label for="speed-standard">Standard</label>
</div>
<div class="thin-choice-row">
<input class="thin-choice" id="speed-express" type="radio" name="speed" value="express">
<label for="speed-express">Express</label>
</div>
</fieldset>
<button type="submit">Continue</button>
</form>Anatomy
- fieldset.thin-fieldset with legend
- Same-name input[type=radio] controls inside thin-choice hosts
- One label per option
- Optional thin-choice-row for alignment
Variants and sizes
- Vertical list of options โ shown above
- Wrapped intrinsic row inside a cluster โ shown above
- Grouped inside a form-validation recipe โ shown above
States and difficult content
Rendered fixtures exercise authored content and constrained layouts with the same production assets.
- Unselected
- Selected
- Focused (roving native radio focus)
- Disabled option
- Required group error
- Forced colors
- Long localized label
- Narrow container
Structured state fixtures are not published for this item yet.
Interaction and keyboard behavior
Native radio behavior owns arrow-key selection, tab focus into the group, activation, validation, and form submission. Only one option per shared name is selectable.
Accessibility
Provide a fieldset legend that names the group. Every option needs its own visible label. Do not rely on color alone; the native indicator and focus outline remain visible.
- Contract
NATIVE- Basis
- Native checkbox and <fieldset>/<legend> radio controls; no APG widget
- Evidence
tests/unit/library/choice-controls.test.ts
API
Use fieldset.thin-fieldset with a legend. Wrap each option in unregistered <thin-choice> and pair it with a label. Inputs that share a name remain the radio group; native value, checked, disabled, and required stay canonical.
Behavior tier: native
Structured host, property, and example tables are not authored for this item yet. Use the summary above and the primary example.
Fallback
Native radio inputs remain keyboard-operable and submit the selected value without CSS or JavaScript.
Assets and dependencies
/components.css
Dependencies: None
Source: packages/ui/src/items/radio-group/style.ts
Stability evidence
- Native radio group semantics, shared-name contract, and no-JavaScript fixture:
test:tests/unit/library/choice-controls.test.ts - Complete item-specific API, fallback, and difficult-state documentation:
test:tests/unit/library/form-controls-stable.test.ts - Cross-theme, density, localized, narrow-container, zoom, and forced-colors fixture:
review:site/src/pages/quality/index.astro#quality-review
Composition
Derived from the examples and style specs, not authored.