Skip to content

Radio

One of a set, built on the native <input type="radio">, rendered as <input is="root-radio">.

Usage

<input is="root-radio" type="radio" name="option" value="a" data-label="Option A" />
<input is="root-radio" type="radio" name="option" value="b" data-label="Option B" />

Groups

A group is implicit: every radio sharing a name is one, which is what the platform already enforces - single selection and arrow-key roving come free. There is no group component. Give the group a visible name the native way, with a <fieldset> and a <legend>; the radios inside expose it as role="radiogroup" themselves, and [direction="horizontal"] lays them on one line.

Choose a plan
Choose a plan
<fieldset>
  <legend>Choose a plan</legend>
  <input is="root-radio" type="radio" name="plan" value="free" data-label="Free" />
  <input is="root-radio" type="radio" name="plan" value="pro" data-label="Pro" />
  <input is="root-radio" type="radio" name="plan" value="team" data-label="Team" />
</fieldset>
Billing
Billing
<fieldset direction="horizontal">
  <legend>Billing</legend>
  <input is="root-radio" type="radio" name="billing" value="monthly" data-label="Monthly" />
  <input is="root-radio" type="radio" name="billing" value="yearly" data-label="Yearly" />
</fieldset>

The group property returns the radios a given one is grouped with - same name, same form, itself included.

The field composition is the shared one (fieldDecorator): a [field-wrapper] span draws the dot, the native input stays in the DOM as [sr-only] - still the focus target and still what assistive tech reads - and data-label, data-hint, required and data-invalid behave as they do on Input.

States

Unchecked is the page ground with a Neutral 400 ring; checked moves the ring into Default 400 and shows a dot at half the control. One name is one answer, so the disabled pair below is a group of its own - two checked radios sharing a name means the last one wins and the first renders unchecked. It never fills, which is what separates it from a checkbox. Hover and :active walk one and two ramp steps deeper, and the whole label is the target.

State
Focus
Disabled
State
Focus
Disabled
<fieldset>
  <legend>State</legend>
  <input is="root-radio" type="radio" name="state" value="off" data-label="Unchecked" />
  <input is="root-radio" type="radio" name="state" value="on" data-label="Checked" checked />
</fieldset>
<fieldset>
  <legend>Focus</legend>
  <input is="root-radio" type="radio" name="state-focus" value="off" data-label="Focused" data-demo="focus" />
  <input is="root-radio" type="radio" name="state-focus" value="on" data-label="Focused, checked" checked data-demo="focus" />
</fieldset>
<fieldset>
  <legend>Disabled</legend>
  <input is="root-radio" type="radio" name="state-disabled" value="off" data-label="Disabled" disabled />
  <input is="root-radio" type="radio" name="state-disabled" value="on" data-label="Disabled, checked" checked disabled />
</fieldset>

Sizes

data-size takes small, medium and large; medium is the default and needs no value written. The control measures 24 / 20 / 16px with a dot at half of that, and small drops to a 1px ring - at 16px a 2px one eats the control. The label follows: the md type step at large and medium, sm at small.

Size
Size
<fieldset>
  <legend>Size</legend>
  <input is="root-radio" type="radio" name="size" value="lg" data-size="large" data-label="Large" checked />
  <input is="root-radio" type="radio" name="size" value="md" data-label="Medium" />
  <input is="root-radio" type="radio" name="size" value="sm" data-size="small" data-label="Small" />
</fieldset>

Description

data-hint puts a description under the label, indented past the control so it sits under the text and reads as this option's rather than the next one's.

Plan
Plan
<fieldset>
  <legend>Plan</legend>
  <input
    is="root-radio"
    type="radio"
    name="tier"
    value="free"
    data-label="Free"
    data-hint="One project, community support."
    checked
  />
  <input
    is="root-radio"
    type="radio"
    name="tier"
    value="pro"
    data-label="Pro"
    data-hint="Unlimited projects, and a person to email."
  />
</fieldset>

Label placement

The label sits after the dot by default. data-label-position moves it to any of the four sides - inline-start, block-start, inline-end (the default), block-end - logically, so the sides follow the writing mode.

All four below, clockwise from the top, stacked in a <fieldset>: the group is the component's own (see above), and it lays its rows out in a column - which is what lets the two block placements be read as above and below rather than as two columns that happen to be taller than the rest of the row.

<fieldset>
  <input is="root-radio" type="radio" name="placement" data-label="Above the dot" data-label-position="block-start" />
  <input is="root-radio" type="radio" name="placement" data-label="After the dot" data-label-position="inline-end" />
  <input is="root-radio" type="radio" name="placement" data-label="Below the dot" data-label-position="block-end" />
  <input is="root-radio" type="radio" name="placement" data-label="Before the dot" data-label-position="inline-start" />
</fieldset>

Focus

The ring is the foundations', drawn around the wrapper: the input is [sr-only] but never hidden, so it is what takes focus and _focus.css is what draws it. Inside a group the arrow keys move it, which is the platform's own behavior.

Focus only exists while something has it, so a page cannot show it by asking. Marked data-demo="focus" - the docs' own attribute, not a component one - the example below wears the same ring, from the same tokens, on the element the real one lands on:

Focus
Focus
<fieldset direction="horizontal">
  <legend>Focus</legend>
  <input is="root-radio" type="radio" name="focus" value="off" data-label="Focused" data-demo="focus" />
  <input is="root-radio" type="radio" name="focus" value="on" data-label="Focused, checked" checked data-demo="focus" />
</fieldset>

data-invalid sets aria-invalid on the control; data-error replaces the description with the message while it is set.

<input is="root-radio" type="radio" name="plan" value="pro" data-label="Pro" data-invalid="true" />

Properties

Property Type Description
group HTMLInputElement[] The radios sharing this one's name, in document order

Attributes

Attribute Type Default Description
type 'radio' 'radio' Input type
name string - Radio group name
checked boolean false Checked state
disabled boolean false Disables the input
data-size 'small' | 'medium' | 'large' 'medium' Control, dot, ring and type step
data-label string - Label text; the component builds the <label>
data-hint string - Description under the label
required boolean false Marks the field required and draws the asterisk
data-label-position 'inline-start' | 'block-start' | 'inline-end' | 'block-end' 'inline-end' Which side the label text sits on
data-invalid 'true' - Marks the control invalid: sets aria-invalid
data-error string - Message shown in place of the description while invalid

Events

Event Detail Description
root-radio.input { value } Fired on input
root-radio.change { value } Fired on change
root-radio.focus { value } Fired on focus
root-radio.blur { value } Fired on blur
root-radio.invalid { value } Fired on validation failure
root-radio.data-label.change { value, oldValue } Fired when the label text changes

Extends

HTMLInputElement - use with <input is="root-radio">.