Skip to content

Radio <input>

Radios let users pick a single option from a set of mutually exclusive choices.

HTML tag<input>
Native
Progressive
Plain
Shadow-DOM
Native element
html
<fieldset>
  <legend>Plan</legend>
  <l-form-field>
    <label>Free</label>
    <input
      type="radio"
      name="plan"
      value="free"
      checked
    />
  </l-form-field>
  <l-form-field>
    <label>Pro</label>
    <input
      type="radio"
      name="plan"
      value="pro"
    />
  </l-form-field>
</fieldset>

l-form-field auto-styles a bare <input type="radio"> and wires the accessibility (label, hint, error, aria-*); standalone, apply .l-radio to the input yourself. Radios that share a name form a group — selecting one deselects the others, and arrow keys move between them. Wrap the group in a <fieldset> with a <legend> for the shared question.

For two opposite options where neither is a sensible default, prefer a single checkbox or a switch. For more than ~5 options, prefer a select.

Options

Selected

Native checked attribute. Only one radio per name group can be selected.

Disabled

Native disabled attribute.

States

Code
html
<div class="flex flex-col gap-3">
  <l-form-field>
    <label>Unselected</label>
    <input
      type="radio"
      name="states"
    />
  </l-form-field>
  <l-form-field>
    <label>Selected</label>
    <input
      type="radio"
      name="states"
      checked
    />
  </l-form-field>
  <l-form-field>
    <label>Disabled</label>
    <input
      type="radio"
      name="states-disabled"
      disabled
    />
  </l-form-field>
  <l-form-field>
    <label>Disabled selected</label>
    <input
      type="radio"
      name="states-disabled-selected"
      checked
      disabled
    />
  </l-form-field>
  <l-form-field invalid>
    <label>Invalid (required, none selected)</label>
    <input
      type="radio"
      name="states-invalid"
      required
    />
  </l-form-field>
</div>

Invalid

Styled via :user-invalid (after interaction) or by setting aria-invalid="true". Inside l-form-field this is managed for you.

Size & accent

Override --size for the box and --accent for the selected fill.

Code
html
<div class="flex flex-col gap-3">
  <l-form-field>
    <label>Default</label>
    <input
      type="radio"
      name="sizing-1"
      checked
    />
  </l-form-field>
  <l-form-field>
    <label>Large</label>
    <input
      type="radio"
      class="[--size:1.5rem]"
      name="sizing-2"
      checked
    />
  </l-form-field>
  <l-form-field>
    <label>Extra large, dark accent</label>
    <input
      type="radio"
      class="[--size:2rem] [--accent:var(--l-color-gray-900)]"
      name="sizing-3"
      checked
    />
  </l-form-field>
</div>

Examples

Group

Wrap the group in a <fieldset> with a <legend> so assistive tech announces the shared question.

Notifications
Code
html
<fieldset class="m-0 border-0 p-0">
  <legend class="mb-3 text-sm font-medium">Notifications</legend>
  <div class="flex flex-col gap-3">
    <l-form-field>
      <label>All new messages</label>
      <input
        type="radio"
        name="notifications"
        value="all"
        checked
      />
    </l-form-field>
    <l-form-field>
      <label>Direct messages and mentions</label>
      <input
        type="radio"
        name="notifications"
        value="mentions"
      />
    </l-form-field>
    <l-form-field>
      <label>Nothing</label>
      <input
        type="radio"
        name="notifications"
        value="none"
      />
    </l-form-field>
  </div>
</fieldset>

With a hint

l-form-field wires the accessibility (label, hint, aria-*). Each option is its own field; group them inside a <fieldset>.

Plan

For individuals getting started.

For growing teams that need more.

Code
html
<fieldset class="flex flex-col gap-3 border-0 p-0 m-0">
  <legend class="mb-2 font-medium">Plan</legend>
  <l-form-field>
    <label>Free</label>
    <input
      type="radio"
      name="plan"
      value="free"
      checked
    />
    <p class="l-hint">For individuals getting started.</p>
  </l-form-field>
  <l-form-field>
    <label>Pro</label>
    <input
      type="radio"
      name="plan"
      value="pro"
    />
    <p class="l-hint">For growing teams that need more.</p>
  </l-form-field>
</fieldset>

Accessibility

Criteria

Role

Uses native <input type=radio> — built-in radio semantics, grouped into a radiogroup by a shared name inside a fieldset

WCAG4.1.2
RGAA11.1
Group name

Wrap the group in <fieldset> with a <legend> so the shared question is announced

WCAG1.3.1
RGAA11.5
Accessible name

Each radio must have an associated <label> (wrap the input or use for/id)

WCAG1.3.1
RGAA11.1
Target size

The label is part of the click target; keep the interactive area at least 24×24px

WCAG2.5.8
Focus visible

Keyboard focus shows a 2px outline via :focus-visible

WCAG2.4.7
RGAA10.7
Required state

Native required on the group communicates a mandatory choice to assistive tech

WCAG3.3.2
RGAA11.10

Keyboard interactions

Tab
Moves focus into the group (to the selected radio, or the first if none is selected)
Arrow keys
Moves between radios in the group and selects the focused one
Space
Selects the focused radio

API reference

Importing

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

Attributes & Properties

nameAttribute
Groups radios so only one can be selected at a time. Every radio in a group shares the same name.
valueAttribute
The value submitted with the form when this radio is the selected one in its group.
checkedAttribute
Whether the radio is selected.
disabledAttribute
Disables the radio.
requiredAttribute
Marks the group as required for form submission.
aria-invalidAttribute
Set to true to force the invalid style (otherwise applied via :user-invalid). l-form-field manages this automatically.

CSS classes

.l-radioClass
Base radio style, applied to <input type="radio">. Inside l-form-field a bare radio is auto-styled, so the class is optional there.

CSS custom properties

--sizedefault:1.25emCustom property
Box size.
--accentdefault:var(--l-form-control-activated-color)Custom property
Selected fill color.
--dotCustom property
Selected dot icon as a url(). Override to swap the SVG (color is baked into the image).