Skip to content

Components

Radio

See also: Radio group

html
<!-- Checked -->
<label class="radio">
  <input id="radioa2" name="basic-enabled" type="radio" checked />
  <span class="sr-only">Checked</span>
</label>

<!-- Unchecked -->
<label class="radio">
  <input id="radioa1" name="basic-enabled" type="radio" />
  <span class="sr-only">Unchecked</span>
</label>

<!-- Unchecked & disabled -->
<label class="radio">
  <input id="radioa3" name="basic-disabled" type="radio" disabled />
  <span class="sr-only">Disabled</span>
</label>

<!-- Checked & disabled -->
<label class="radio">
  <input id="radioa4" name="basic-disabled" type="radio" checked disabled />
  <span class="sr-only">Checked and disabled</span>
</label>

Visible label

Render the label text inside an element with a .text class.

html
<label class="radio">
  <input id="radio-example-id" name="labels" type="radio" />
  <span class="label">Label</span>
</label>

Label position

html
<label class="radio stack">
  <input name="radio-label-position" type="radio">
  <span class="label">Stack</span>
</label>

Supporting text

html
<label class="radio">
  <input name="checkbox" type="radio">
  <span class="label">Stack</span>
  <span class="supporting-text">Supporting text</span>
</label>

Sizes

html
<label class="radio small">
  <input id="radio-small-1" name="size-enabled" type="radio" />
  <span class="label">Small</span>
</label>

Field group

Legend
html
<fieldset class="field-group">
  <legend>Legend</legend>
  <div class="fields">
    <!--  -->
  </div>
</fieldset>

Direction

Legend
html
<fieldset class="field-group row">
  <!--  -->
</fieldset>

Supporting text

Can be placed above and below the fields.

LegendSupporting text above fields
Legend
Supporting text below fields
html
<fieldset class="field-group row">
  <legend>Legend</legend>
  <span class="supporting-text">Supporting text</span>
  <div class="fields">
    <!--  -->
  </div>
</fieldset>

<fieldset class="field-group row">
  <legend>Legend</legend>
  <div class="fields">
    <!--  -->
  </div>
  <span class="supporting-text">Supporting text</span>
</fieldset>

Disabled

Attach the disabled attribute to the <fieldset> element.

Legend
html
<fieldset class="field-group row" disabled>
  <!--  -->
</fieldset>

Required

Attach the required attribute to at least one of your <input> elements.

These are required!
html
<fieldset class="field-group row">
  <legend>Legend</legend>
  <div class="fields">
    <label class="radio">
      <input name="field-group-required-1" type="radio" required />
      <span class="label">Radio 1</span>
    </label>
    <!--  -->
  </div>
</fieldset>

Validation

Attach the .error class to your fieldset.field-group element

LegendSomething went wrong!
html
<fieldset class="field-group row error">
  <!--  -->
</fieldset>

Accessibility

Labels

Accessible radio buttons must have a label. You can choose between three approaches:

ApproachUsage in Radio component
Provide a label text inside the label/role="radio" elementDefault
Add a aria-label on the input elementNot used
Have a visible label that you reference with aria-labelledbyNot used

Keyboard support

KeyFunction
SpaceWhen Radio is focused it changes its state.
Enter(Optional) When Radio is focused it changes its state.

API

TypeModifiersDefaultDescription
Children.label, .supporting-text-Optional children.
Label position.stack, defaultdefaultIf applied, the label is stacked under the element.
Sizes.small, default, .largedefaultThe size of the element.
Validation.error-When applied, error styles are shown.

Field group API

TypeModifiersDefaultDescription
Children<legend>, .fields, .supporting-text, checkbox, radio, switch-Supported child elements.
Directioncolumn, .rowcolumnDecides which direction the inputs will be placed.
Disabled[disabled]-When applied, disabled styles are shown.
Validation.error-When applied, error styles are shown.

Installation

css
@layer components.base {
  label:where(.checkbox, .radio) {
    align-items: center;
    color: var(--text-color-1);
    cursor: pointer;
    display: inline-grid;
    gap: 0 var(--size-2);
    grid-auto-columns: auto;
    grid-auto-flow: column;
    inline-size: fit-content;
    line-height: 1.5;
    transform: translateZ(0);
    user-select: none;

    /* Disabled */
    &:has([disabled]) {
      cursor: not-allowed;
      opacity: 0.64;
      user-select: none;

      input {
        cursor: not-allowed;
      }
    }

    /* Required dot */
    &:has([required]:not([type="checkbox"]:checked)) {
      .label:after {
        color: var(--red);
        content: "*";
        inset: 0 -0.25ex auto auto;
        position: absolute;
      }
    }

    /* Label */
    .label {
      grid-column: 2;
      grid-row: 1;
      position: relative;
      padding-inline: 0 1ex;
    }

    /* Supporting text */
    .supporting-text {
      color: var(--text-color-2);
      font-size: var(--font-size-xs);
      grid-column: 2;
      grid-row: 2;
      line-height: 1.5;
      z-index: 1;
    }

    /* Stacked layout */
    &.stack {
      justify-items: center;
      grid-auto-columns: unset;

      .label {
        grid-column: 1/-1;
        grid-row: 2;
        margin-block-start: var(--size-1);
        padding-inline: 1ex;

        /* Required dot */
        &:after {
          inset: 0 -0.25ex auto auto;
        }
      }

      .supporting-text {
        grid-column: 1/-1;
        grid-row: 3;
      }
    }

    /* Input */
    input {
      aspect-ratio: 1;
      block-size: 1.125rem;
      cursor: pointer;
      inline-size: 1.125rem;

      &:before {
        --highlight-size: 175%;
      }
    }

    /* Sizes */
    &.small {
      input {
        block-size: var(--size-3);
        inline-size: var(--size-3);
      }
    }

    &.large {
      input {
        block-size: var(--size-4);
        inline-size: var(--size-4);
      }
    }

    /* Validation */
    &.error {
      input[type="checkbox"] {
        accent-color: var(--color-9);

        & ~ :where(.label, .supporting-text) {
          color: var(--color-9);
        }
      }
    }

    /* Touch devices */
    @media (pointer: coarse) {
      input {
        block-size: var(--size-4);
        inline-size: var(--size-4);
      }
    }
  }
}
css
@layer components.has-deps {
  /* Common styling for checkbox, radio and switch groups */
  :where(fieldset.field-group) {
    border: 0;
    border-radius: 0;
    gap: 0;
    padding: 0;
    z-index: 1;

    legend {
      color: var(--text-color-2);
      padding: 0 1ex 0 0;
    }

    /* Disabled */
    &[disabled] {
      cursor: not-allowed;
      opacity: 0.64;
      user-select: none;

      input {
        cursor: not-allowed;
      }
    }

    /* Validation */
    &.error {
      legend,
      .supporting-text {
        color: var(--color-9);
      }
    }

    /* Required */
    &:has([required]) {
      &:not(:has(input:where([type="radio"], [type="checkbox"]):checked)) {
        legend {
          position: relative;

          &:after {
            color: var(--red);
            content: "*";
            inset: 0 -0.25ex auto auto;
            position: absolute;
          }
        }
      }
    }
    :where(.radio, .checkbox, .switch) .label:after {
      display: none;
    }

    /* Supporting text */
    .supporting-text {
      color: var(--text-color-2);
      font-size: var(--font-size-xs);
      line-height: 1.5;
      z-index: 1;
    }

    /* Fields */
    .fields {
      display: flex;
      flex-direction: column;
      gap: var(--size-2);

      * ~ & {
        padding: var(--size-2) 0;
      }
    }

    :last-child {
      padding-block-end: 0;
    }

    /* Directions */
    &.row {
      .fields {
        flex-direction: row;
      }
    }
  }
}