Radio
Selects exactly one option among visible, related alternatives.
Design
Functional preview
Running with stable DS HTML and CSS, without a component JavaScript runtime.
Loading functional preview…
Open the HTML/CSS/JS playground
Anatomy
1 Label (
2 Input (
3 Dot — CSS pseudo-element, visible when selected.
4 Group (
5 Legend (
.ds-radio-label) — wraps radio + text, extends click target.2 Input (
.ds-radio) — styled native <input type="radio">.3 Dot — CSS pseudo-element, visible when selected.
4 Group (
.ds-radio-group) — <fieldset> wrapping related radios.5 Legend (
.ds-radio-group__legend) — group heading, provides context to screen readers.
Radio Group
Sizes
Error Group
Disabled
States
| State | CSS trigger | Visual change | Token |
|---|---|---|---|
| Unselected | --- | Neutral control fill and border | component.radio.control.fill.unselected.default, component.radio.control.border-color.unselected.default |
| Unselected hover | :hover | Darker neutral control border/fill | component.radio.control.fill.unselected.hover, component.radio.control.border-color.unselected.hover |
| Selected | :checked | Primary control fill with dot | component.radio.control.fill.selected.default, component.radio.dot.fill.selected.default |
| Selected hover | :checked:hover | Darker primary control fill | component.radio.control.fill.selected.hover, component.radio.dot.fill.selected.hover |
| Focus | :focus-visible | 2px outline ring | component.radio.focus-ring.radius.default + component.focus-ring.* |
| Error group | .ds-radio-group--error | Error border on controls and message below group | component.radio.control.border-color.unselected.error, semantic.feedback.error.content-default |
| Disabled | [disabled] | Muted control, dot and label | component.radio.control.fill.*.disabled, semantic.content.disabled |
Figma properties
| Property | Type | Default | Description |
|---|---|---|---|
Show Label | Boolean | true | Shows or hides the control label |
Label | Text | "Rótulo" | Label text (label/md) |
Show Description | Boolean | false | Shows descriptive text below the label |
Description | Text | "Texto descritivo" | Multiline description text (body/sm) |
Show Helper Text | Boolean | false | Shows helper text below the description |
Helper Text | Text | "Texto auxiliar" | Caption/sm annotation (content/secondary) |
Usage
When to use
- Exactly one option must be selected among two to seven visible alternatives.
- Comparing the alternatives before choosing supports the decision.
When not to use
- Zero, one, or multiple options may be selected; use Checkbox.
- There are many alternatives or limited space; use Select.
Implementation
- Status
- Stable
- Distribution
- npm package
Installation
npm install ds-tispnpm add ds-tisyarn add ds-tisbun add ds-tisImports
import 'ds-tis/css'Markup
<fieldset class="ds-radio-group">
<legend class="ds-radio-group__legend">Preferência de contato</legend>
<label class="ds-radio-label">
<input class="ds-radio" type="radio" name="contact" value="email" checked />
<span class="ds-radio__content">
<span class="ds-radio__label">E-mail</span>
</span>
</label>
</fieldset>Web implementation contract
Token mapping
Same tokens as Checkbox except border-radius: 50% (circle) instead of foundation.radius.4.
| Property | Token Component | CSS variable |
|---|---|---|
| control size (sm/md/lg) | component.radio.control.size.* | --ds-radio-control-size-* |
| control fill | component.radio.control.fill.*.* | --ds-radio-control-fill-* |
| control border | component.radio.control.border-color.*.* | --ds-radio-control-border-color-* |
| dot size/fill | component.radio.dot.{size,fill}.* | --ds-radio-dot-* |
| target height | component.radio.target.height.* | --ds-radio-target-height-* |
| focus ring | component.radio.focus-ring.radius.default + component.focus-ring.* | --ds-focus-ring-* + --ds-radio-focus-ring-radius-default |
| label/helper/description/error | component.form-field.* | --ds-form-field-* |
CSS classes
| Class | Description |
|---|---|
ds-radio | Custom styled native radio button |
ds-radio-label | Label wrapper (wraps radio + text) |
ds-radio-group | Fieldset container for a group of radios |
ds-radio-group__legend | Group legend/heading |
ds-radio--sm | Small size (16px) |
ds-radio--lg | Large size (24px) |
ds-radio-group--error | Error state for the group |
ds-radio-group__error | Error message text for the group |
ds-radio__content | Vertical frame stacking label, description, and helper text |
ds-radio__label | Label text inside content frame (label/md) |
ds-radio__description | Multiline description text (body/sm, content/default) |
ds-radio__helper | Helper annotation (caption/sm, content/secondary) |
Accessibility
Keyboard interaction
| Key | Action |
|---|---|
Tab | Moves focus into the radio group (single tab stop) or out of it |
Arrow Up / Arrow Left | Selects the previous option in the group |
Arrow Down / Arrow Right | Selects the next option in the group |
A radio group behaves as a single tab stop. Arrow keys navigate between options within the group, and selection moves with focus.
Accessibility
| WCAG criterion | Requirement | Status |
|---|---|---|
| 1.3.1 Info and Relationships (A) | Group in <fieldset> + <legend> | ✓ |
| 2.4.11 Focus Appearance (AA) | Focus ring visible via :focus-visible | ✓ |
| 2.5.8 Target Size min (AA) | Label extends click target beyond the radio itself | ✓ |
| 4.1.2 Name, Role, Value (A) | Native <input type="radio"> provides role + state automatically | ✓ |
| 3.3.1 Error Identification (A) | Group error communicated via ds-radio-group--error | ✓ |
Implementation notes
Always wrap radio groups in a <fieldset> with a <legend> to provide group context to screen readers. Uses native <input type="radio"> for built-in keyboard navigation -- arrow keys move between options within a group, Tab enters and exits the group. Focus ring is visible on keyboard focus via :focus-visible.
Invisible label — requires explicit ARIA
When Show Label = false and Show Description = true, link the <input> to the description via aria-labelledby="description-id". If neither is visible, use aria-label directly on the <input>. Helper Text should always be linked via aria-describedby="helper-id".
Output responsibility
Preserve native element semantics, accessible names, keyboard behavior, and visible focus.
Validation evidence
Covered by the stable Storybook, public runtime tests when applicable, keyboard scenarios, responsive checks, and Axe.