Skip to content

Radio group <fieldset>

A set of native radios, shown as the classic dot or as joined buttons — the form counterpart of a segmented control.

HTML tag<fieldset>
Native
Progressive
Plain
Shadow-DOM
Native element
html
<fieldset class="l-radio-group">
  <legend>View</legend>
  <label>
    <input
      type="radio"
      class="l-radio"
      name="view"
      value="list"
      checked
    />
    List
  </label>
  <label>
    <input
      type="radio"
      class="l-radio"
      name="view"
      value="board"
    />
    Board
  </label>
</fieldset>

The group is a <fieldset> captioned by its <legend>, and each item is a <label> wrapping one <input type="radio">. That is the platform's own way to group and name a set of radios, so there is no ARIA to add and no for/id pair to keep in sync. There is no JavaScript either: radios sharing a name already give single selection, arrow-key navigation, form submission, reset and validation — this is a stylesheet, not a component.

That is the difference with segmented control: use this when the value is submitted with a form, and the segmented control when the choice takes effect immediately.

Options

Appearance

Default

The radio primitive: put .l-radio on each input.

Notifications
Code
html
<fieldset
  class="l-radio-group"
  data-orientation="vertical"
>
  <legend>Notifications</legend>
  <label>
    <input
      type="radio"
      class="l-radio"
      name="notifications"
      value="all"
      checked
    />
    All new messages
  </label>
  <label>
    <input
      type="radio"
      class="l-radio"
      name="notifications"
      value="mentions"
    />
    Direct messages and mentions
  </label>
  <label>
    <input
      type="radio"
      class="l-radio"
      name="notifications"
      value="none"
    />
    Nothing
  </label>
</fieldset>

Button

Add data-appearance="button" on the group and .l-button on each label. The items are joined into a single unit and the selected one keeps the button's active fill.

View
Code
html
<fieldset
  class="l-radio-group"
  data-appearance="button"
>
  <legend>View</legend>
  <label class="l-button">
    <input
      type="radio"
      name="view"
      value="list"
      checked
    />
    List
  </label>
  <label class="l-button">
    <input
      type="radio"
      name="view"
      value="board"
    />
    Board
  </label>
  <label class="l-button">
    <input
      type="radio"
      name="view"
      value="calendar"
    />
    Calendar
  </label>
</fieldset>

Naming the group

The <legend> names the group — the technique WCAG H71 and RGAA 11.6 ask for. When no visible caption fits, a view switcher in a toolbar for instance, drop the <legend> and name the <fieldset> with aria-label instead.

html
<fieldset
  class="l-radio-group"
  data-appearance="button"
  aria-label="View"
>
  <label class="l-button">…</label>
</fieldset>

Orientation

Add data-orientation="vertical" to stack the items. Works in both appearances.

Delivery
Code
html
<fieldset
  class="l-radio-group"
  data-appearance="button"
  data-orientation="vertical"
>
  <legend>Delivery</legend>
  <label class="l-button">
    <input
      type="radio"
      name="delivery"
      value="standard"
      checked
    />
    Standard
  </label>
  <label class="l-button">
    <input
      type="radio"
      name="delivery"
      value="express"
    />
    Express
  </label>
  <label class="l-button">
    <input
      type="radio"
      name="delivery"
      value="pickup"
    />
    Pickup
  </label>
</fieldset>

Sizes

In the button appearance, set data-size on every label so the group stays even. Any .l-button size works.

Small
Medium
Large
Code
html
<fieldset
  class="l-radio-group"
  data-appearance="button"
>
  <legend>Small</legend>
  <label
    class="l-button"
    data-size="sm"
  >
    <input
      type="radio"
      name="density-sm"
      value="compact"
      checked
    />
    Compact
  </label>
  <label
    class="l-button"
    data-size="sm"
  >
    <input
      type="radio"
      name="density-sm"
      value="cosy"
    />
    Cosy
  </label>
</fieldset>

<fieldset
  class="l-radio-group"
  data-appearance="button"
>
  <legend>Medium</legend>
  <label class="l-button">
    <input
      type="radio"
      name="density-md"
      value="compact"
      checked
    />
    Compact
  </label>
  <label class="l-button">
    <input
      type="radio"
      name="density-md"
      value="cosy"
    />
    Cosy
  </label>
</fieldset>

<fieldset
  class="l-radio-group"
  data-appearance="button"
>
  <legend>Large</legend>
  <label
    class="l-button"
    data-size="lg"
  >
    <input
      type="radio"
      name="density-lg"
      value="compact"
      checked
    />
    Compact
  </label>
  <label
    class="l-button"
    data-size="lg"
  >
    <input
      type="radio"
      name="density-lg"
      value="cosy"
    />
    Cosy
  </label>
</fieldset>

Disabled segment

Add disabled to a segment's input. Arrow keys skip it, and it drops out of the tab order — both from the browser, not from us.

Plan
Code
html
<fieldset
  class="l-radio-group"
  data-appearance="button"
>
  <legend>Plan</legend>
  <label class="l-button">
    <input
      type="radio"
      name="plan"
      value="free"
      checked
    />
    Free
  </label>
  <label class="l-button">
    <input
      type="radio"
      name="plan"
      value="pro"
    />
    Pro
  </label>
  <label class="l-button">
    <input
      type="radio"
      name="plan"
      value="enterprise"
      disabled
    />
    Enterprise
  </label>
</fieldset>

Accessibility

Criteria

Role

Native <input type="radio"> controls expose radio and their checked state without any ARIA

WCAG4.1.2
RGAA11.1
Group name

The <fieldset> groups the radios and its <legend> names them, natively. Without a visible caption, aria-label on the fieldset

WCAG1.3.1
RGAA11.5
Segment name

The <label> wraps its input, so its text is the accessible name — no for/id pair needed

WCAG4.1.2
RGAA11.1
Keyboard

Arrow keys move and select through the group; the group is a single Tab stop. Browser behaviour, not scripted

WCAG2.1.1
RGAA7.3
Focus ring

In the button appearance the input is transparent but focusable, so the ring is mirrored onto its label and raised above the joined borders

Target size

In the button appearance a segment is a full .l-button, so it clears the 24px minimum at every size

WCAG2.5.8

Rules

  • The group is a <fieldset>, named by a <legend> first child — or by aria-label when no visible caption fits
  • Every input in the group shares one name
  • Wrap each input in a <label> — that names it and makes it the click target, with no for/id pair
  • For the button appearance, set data-appearance="button" on the group and .l-button on every label; otherwise put .l-radio on every input
  • Never hide the input with display: none or visibility: hidden: it would leave the accessibility tree and the tab order

Keyboard interactions

Tab
Moves focus into the group, onto the checked segment
ArrowRight
Selects and focuses the next segment, wrapping
ArrowLeft
Selects and focuses the previous segment, wrapping
ArrowDown
Same as ArrowRight
ArrowUp
Same as ArrowLeft
Space
Selects the focused segment when none was selected

API reference

Importing

css
@import 'luxen-ui/css/radio-group';

The segments are .l-button elements, so luxen-ui/css/button is required too.

Attributes & Properties

aria-labelAttribute
Names the group when no visible <legend> fits — a view switcher in a toolbar, say. Prefer a <legend>: it is the technique WCAG H71 and RGAA 11.6 ask for.
data-appearanceAttribute
button — Renders the items as joined buttons: put .l-button on each label. Omit it for the radio primitive: put .l-radio on each input.
data-orientationhorizontal | verticalAttribute
Stacks the items. Defaults to horizontal.

CSS classes

.l-radio-groupClass
Applied to the <fieldset> grouping the radios. Its first child is the <legend> naming them; each item is a <label> wrapping one <input type="radio">.