Navigation

v0.0.4
Forms

Radio

A small round well for one choice among several.

On this page

A radio is a small round well that fills with ink and shows a dot when chosen. A circle says "one of these", where a checkbox's square says "any of these". Put .radio on a native <input type="radio">, and wrap it with its text in a <label class="check"> so the whole row is a target.

Radio

<fieldset class="fieldset">  <legend class="fieldset-legend">Visibility</legend>  <label class="check">    <input type="radio" class="radio" name="visibility" value="public" checked>Public</label>  <label class="check">    <input type="radio" class="radio" name="visibility" value="team">Team only</label>  <label class="check">    <input type="radio" class="radio" name="visibility" value="private">Private</label></fieldset>

#A group

Radios that share a name form one group: choosing one clears the others, and the arrow keys move the choice along the group. Put the group in a fieldset with a legend, which names the question the options answer.

#In a row

A few short options can sit side by side. Keep the gap between options wider than the gap inside one, so each label belongs to its own circle.

In a row

<fieldset class="fieldset">  <legend class="fieldset-legend">Billing period</legend>  <div class="flex flex-wrap gap-lg">    <label class="check">      <input type="radio" class="radio" name="period" checked>Monthly</label>    <label class="check">      <input type="radio" class="radio" name="period">Yearly</label>  </div></fieldset>

#Disabled

A disabled radio fades, and its label drops to disabled ink. Disable one option, or the whole group by disabling its fieldset.

Disabled

<fieldset class="fieldset">  <legend class="fieldset-legend">Region</legend>  <label class="check">    <input type="radio" class="radio" name="region" checked>Tokyo</label>  <label class="check">    <input type="radio" class="radio" name="region">Frankfurt</label>  <label class="check">    <input type="radio" class="radio" name="region" disabled>Virginia, at capacity</label></fieldset>

#Choosing a control

Radios show every option at once and take a row each. Other controls make the same single choice in other shapes:

  • A segmented control for two to five short options in one row, such as a view switch.
  • A select when there are more options than fit, or space is tight.
  • A choice when each option needs a title and a sentence.
  • Chips for a filter that changes a view at once.

#Accessibility

  • Give every radio in a group the same name. Without it they do not exclude each other and the arrow keys do not move between them.
  • A group has one tab stop. Tab reaches the chosen radio, or the first when none is chosen, and the arrow keys move from there.
  • Start with one option chosen when there is a sensible default. A group with nothing chosen cannot be returned to that state by the reader.

#Classes

ClassDescription
radioA native type="radio" input: a round well

#Source

@master/ui/components/radio.css
/* * Radio: a native input drawn as a small round well that fills with ink and shows a dot when chosen. * A circle says "one of these", where a checkbox's square says "any of these". * Wrap it with its text in a .check label, which checkbox.css defines, to make the whole row a target. */@layer components {  .radio {    --check-size: 1.125rem;    position: relative;    display: inline-grid;    flex: none;    place-items: center;    width: var(--check-size);    height: var(--check-size);    /* Centres the control on the first line of a label that wraps. */    margin: max(0px, calc((1lh - var(--check-size)) / 2)) 0;    padding: 0;    border: 0;    border-radius: 999px;    background: var(--color-surface-inset);    /* A small control needs a firmer edge than a surface to be found: one step, still half a pixel. */    box-shadow: var(--shadow-pressed), inset 0 0 0 .5px var(--color-line-strong);    vertical-align: middle;    appearance: none;    cursor: inherit;    transition-property: background-color, box-shadow;    transition-duration: var(--duration-quick);    transition-timing-function: var(--easing-glide);    /* The dot: a third of the circle, in the ink of the page behind it. */    &::after {      content: '';      width: 6px;      height: 6px;      border-radius: 999px;      opacity: 0;      scale: .4;      background: var(--color-text-inverse);      /* The dot leaves at once and arrives with a short settle. */      transition: opacity var(--duration-press) linear, scale var(--duration-press) linear;    }    @media (hover: hover) {      &:hover:not(:checked, :disabled) {        background: var(--color-surface-inset-hover);        box-shadow: var(--shadow-pressed), inset 0 0 0 .5px var(--color-text-muted);      }    }    &:checked {      background: var(--color-surface-inverse);      box-shadow: var(--shadow-ink-mark);      &::after {        opacity: 1;        scale: 1;        transition: opacity var(--duration-quick) var(--easing-glide), scale var(--duration-base) var(--easing-glide);      }    }    &:focus-visible {      @apply --focus-ring;    }    &:disabled {      opacity: .5;    }    @media (prefers-reduced-motion: reduce) {      &::after {        scale: 1;      }    }    /* Draw with system colors so the mark survives forced colors. */    @media (forced-colors: active) {      forced-color-adjust: none;      background: Field;      box-shadow: inset 0 0 0 1px FieldText;      &::after {        background: HighlightText;      }      &:checked {        background: Highlight;        box-shadow: none;      }      &:disabled {        opacity: 1;        box-shadow: inset 0 0 0 1px GrayText;      }      &:focus-visible {        outline-color: Highlight;      }    }  }}