Radio Group

ui-radio-group turns its <option> children into native radio buttons with labels, laid out in a column or a row. Choosing a radio sets the group's value. The host has the radiogroup role and is form-associated. The chosen option's value is submitted under the group's name.

Because the radios are native inputs that share a name inside the group's shadow root, the arrow keys move between them as usual.

Previewindex.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Radio Group demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/radio-group.html"></import-element>

  <ui-radio-group aria-label="Plan" value="pro">
    <option value="free">Free</option>
    <option value="pro">Pro</option>
    <option value="team">Team</option>
  </ui-radio-group>
</body>
</html>

Examples

Horizontal

Previewindex.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Radio Group demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/radio-group.html"></import-element>

  <ui-radio-group aria-label="Size" orientation="horizontal" value="m">
    <option value="s">Small</option>
    <option value="m">Medium</option>
    <option value="l">Large</option>
  </ui-radio-group>
</body>
</html>

Disabled options

disabled on an option disables that radio. disabled on the group disables all of them.

Previewindex.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Radio Group demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/radio-group.html"></import-element>

  <ui-radio-group aria-label="Shipping" value="standard">
    <option value="standard">Standard (5 days)</option>
    <option value="express">Express (2 days)</option>
    <option value="overnight" disabled>Overnight (unavailable)</option>
  </ui-radio-group>
  <ui-radio-group aria-label="Locked plan" value="free" disabled>
    <option value="free">Free</option>
    <option value="pro">Pro</option>
  </ui-radio-group>
</body>
</html>

Option labels

The radio label is the option's label attribute, or its text when there is no label.

Previewindex.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Radio Group demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/radio-group.html"></import-element>

  <ui-radio-group aria-label="Theme" value="system">
    <option value="light" label="Light theme">light</option>
    <option value="dark" label="Dark theme">dark</option>
    <option value="system" label="Follow the system">system</option>
  </ui-radio-group>
</body>
</html>

Listening for changes

Previewindex.htmlradio-group-change-demo.html

Density:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Radio Group demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/radio-group.html"></import-element>

  <import-element from="./radio-group-change-demo.html"></import-element>
  <radio-group-change-demo></radio-group-change-demo>
</body>
</html>
<custom-element name="radio-group-change-demo">
  <script>
    const density = useState({ value: "comfortable" });

    function changed(group) {
      density.value = group.value;
    }
  </script>

  <style>
    :host {
      display: grid;
      gap: calc(var(--spacing) * 3);
    }
    p {
      margin: 0;
      font-size: var(--text-sm);
      color: var(--muted-foreground);
    }
  </style>

  <ui-radio-group aria-label="Density" value="comfortable" onchange="changed(this)">
    <option value="compact">Compact</option>
    <option value="comfortable">Comfortable</option>
    <option value="spacious">Spacious</option>
  </ui-radio-group>
  <p>Density: <slot name="#density.value"></slot></p>
</custom-element>

In a form

The chosen value is submitted under the group's name. Nothing is submitted while no option is chosen. Reset returns the group to its value attribute.

Previewindex.htmlradio-group-form-demo.html
Continue Reset
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Radio Group demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/button.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/radio-group.html"></import-element>

  <import-element from="./radio-group-form-demo.html"></import-element>
  <radio-group-form-demo></radio-group-form-demo>
</body>
</html>
<custom-element name="radio-group-form-demo">
  <script>
    const result = useState({ text: "Submit the form to see its data." });

    function submit(event) {
      event.preventDefault();
      const entries = [...new FormData(event.target)].map(([name, value]) => `${name}=${value}`);
      result.text = entries.join("\n") || "(nothing submitted)";
    }

    function reset() {
      result.text = "The form was reset.";
    }
  </script>

  <style>
    :host {
      display: grid;
      gap: calc(var(--spacing) * 4);
      width: 100%;
      max-width: calc(var(--spacing) * 80);
    }
    form {
      display: grid;
      gap: calc(var(--spacing) * 4);
    }
    .actions {
      display: flex;
      gap: calc(var(--spacing) * 2);
    }
    output {
      display: block;
      padding: calc(var(--spacing) * 3);
      border-radius: var(--radius-md);
      background: var(--muted);
      font-family: var(--font-mono);
      font-size: var(--text-xs);
      white-space: pre-wrap;
    }
  </style>

  <form onsubmit="submit(event)" onreset="reset()">
    <ui-radio-group name="billing" aria-label="Billing period" value="monthly">
      <option value="monthly">Monthly</option>
      <option value="yearly">Yearly (2 months free)</option>
    </ui-radio-group>
    <ui-radio-group name="payment" aria-label="Payment method" orientation="horizontal">
      <option value="card">Card</option>
      <option value="invoice">Invoice</option>
    </ui-radio-group>
    <div class="actions">
      <ui-button type="submit">Continue</ui-button>
      <ui-button type="reset" variant="outline">Reset</ui-button>
    </div>
  </form>
  <output><slot name="#result.text"></slot></output>
</custom-element>

API

Attributes

Attribute Type Default Description
value string The value of the chosen option.
orientation "vertical" | "horizontal" "vertical" Lays the radios out in a column or a row.
disabled boolean false Disables every radio. Also true inside a disabled <fieldset>.
required boolean false The form is invalid until an option is chosen.

Properties

Property Type Description
value string The chosen value. Setting it checks the matching radio.

Events

Event Detail Description
change none Fired from the group after the user chooses a radio.

Child elements

Element Attributes read Description
<option> value, label (or text), disabled One radio. Options stay hidden in place. Adding or removing options updates the radios.

CSS parts

Part Description
item The <label> around each radio and its text.
control Each native <input type="radio">.

CSS states

State Description
:state(attr-orientation-is-horizontal) Set for the horizontal layout.

Forms

ui-radio-group is form-associated. It submits value under its name, or nothing while no option is chosen. With required and no value it reports valueMissing with the message "Please select an option.", and the validation bubble points at the first enabled radio, which takes focus. form.reset() restores the value attribute.

Accessibility

The host has role="radiogroup". Name it with aria-label or aria-labelledby. Each radio is a native input inside its own <label>, so the option text is its name and clicking the text checks it. Tab moves into the group on the checked radio, and the arrow keys move and check the next or previous radio.

Edit this page on GitHub · View as Markdown