Radio

A native <input type="radio"> styled as a small round dot. Group radios by a shared name to enforce single-selection within the group.

Basic

Add .radio to the input. Wrap the input and label in .field-row. Group radios by giving them a shared name, and the browser handles single-selection within the group.

<div class="demo-stack" style="max-width: 24rem;">
  <div class="field-row">
    <input class="radio" type="radio" name="defaultRadio" id="defaultRadio1" checked />
    <label class="field-row__label" for="defaultRadio1">First option</label>
  </div>
  <div class="field-row">
    <input class="radio" type="radio" name="defaultRadio" id="defaultRadio2" />
    <label class="field-row__label" for="defaultRadio2">Second option</label>
  </div>
  <div class="field-row">
    <input class="radio" type="radio" name="defaultRadio" id="defaultRadio3" />
    <label class="field-row__label" for="defaultRadio3">Third option</label>
  </div>
</div>

Inline

Add .field-row--inline to line items up on the same row.

<div class="demo-row">
  <div class="field-row field-row--inline">
    <input class="radio" type="radio" name="inlineRadio" id="inlineRadio1" checked />
    <label class="field-row__label" for="inlineRadio1">One</label>
  </div>
  <div class="field-row field-row--inline">
    <input class="radio" type="radio" name="inlineRadio" id="inlineRadio2" />
    <label class="field-row__label" for="inlineRadio2">Two</label>
  </div>
  <div class="field-row field-row--inline">
    <input class="radio" type="radio" name="inlineRadio" id="inlineRadio3" />
    <label class="field-row__label" for="inlineRadio3">Three</label>
  </div>
</div>

Reverse

Add .field-row--reverse to flip the label to the start and the input to the end.

<div class="demo-stack" style="max-width: 24rem;">
  <div class="field-row field-row--reverse">
    <input class="radio" type="radio" name="reverseRadio" id="reverseRadio" checked />
    <label class="field-row__label" for="reverseRadio">Reversed radio</label>
  </div>
</div>

Disabled

Add disabled to dim the input and its label, and block interaction.

<div class="demo-stack" style="max-width: 24rem;">
  <div class="field-row">
    <input class="radio" type="radio" name="disabledRadio" id="disabledRadio1" disabled />
    <label class="field-row__label" for="disabledRadio1">Disabled radio</label>
  </div>
  <div class="field-row">
    <input class="radio" type="radio" name="disabledRadio" id="disabledRadio2" checked disabled />
    <label class="field-row__label" for="disabledRadio2">Disabled, selected</label>
  </div>
</div>

Browser validation

Pair required on any radio in the group with the native :user-invalid pseudo. The browser fires it after the user attempts to submit the form, and clears it the moment any radio in the group is selected.

<form class="demo-stack" style="max-width: 24rem;" onsubmit="event.preventDefault()">
  <div class="field-row">
    <input class="radio" type="radio" name="reqPlan" id="reqPlanBasic" required />
    <label class="field-row__label" for="reqPlanBasic">Basic plan</label>
  </div>
  <div class="field-row">
    <input class="radio" type="radio" name="reqPlan" id="reqPlanPro" required />
    <label class="field-row__label" for="reqPlanPro">Pro plan</label>
  </div>
  <button type="submit" class="btn btn--primary align-self-start">Submit</button>
</form>

Hit Submit without picking to trigger :user-invalid. Pick either option and the red clears on its own.

Server validation

Set aria-invalid="true" from your form library. The attribute is sticky. Stisla just paints the red while the attribute is present. Remove it when your validator considers the field resolved.

<div class="demo-stack" style="max-width: 24rem;">
  <div class="field-row">
    <input class="radio" type="radio" name="srvPlan" id="srvPlanBasic" aria-invalid="true" />
    <label class="field-row__label" for="srvPlanBasic">Basic plan</label>
  </div>
  <div class="field-row">
    <input class="radio" type="radio" name="srvPlan" id="srvPlanPro" aria-invalid="true" checked />
    <label class="field-row__label" for="srvPlanPro">Pro plan (selected, server still flagged)</label>
  </div>
</div>

The red rim stays even after you pick. The checked variant shows both signals at once. Primary fill says "this is selected", red rim says "and the server still considers it invalid".

Without labels

Drop the .field-row wrapper to use a bare .radio. Always pair with an aria-label for accessibility.

<div class="demo-row">
  <input class="radio" type="radio" name="bareRadio" aria-label="Bare radio" />
  <input class="radio" type="radio" name="bareRadio" aria-label="Bare radio, selected" checked />
</div>

Customization

Five variables retune .radio without touching component CSS. Override on the input itself, on a parent scope, or on :root.

Variable Default Use
--radio-size calc(1rem * var(--st-density)) Dot diameter; density scales it
--radio-bg var(--st-surface) Unchecked background
--radio-border var(--st-border) Unchecked border; validation hooks flip this to --st-danger
--radio-checked-bg var(--st-primary) Selected background
--radio-indicator SVG data URL (dot) Glyph painted over --radio-checked-bg. The fill color is a literal because data: URLs can't read CSS variables. To recolor, replace the URL with one whose fill matches the new color