Morphos
Inputs

RadioGroup

A compound component for single-selection radio button groups.

Interactive example

Open in Storybook

Installation

npm install @morphos/inputs
pnpm add @morphos/inputs
yarn add @morphos/inputs
bun add @morphos/inputs

Import

import { RadioGroup, Radio } from '@morphos/inputs'

Compound components

ComponentDescription
RadioGroupRoot container that manages selection state and renders role="radiogroup"
RadioIndividual radio option within the group

Usage

@Component()
class MyComponent extends StatefulComponent {
  @State() group = new RadioGroup({ defaultValue: 'monthly', orientation: 'vertical' })

  render() {
    return (
      <fieldset>
        <legend>Billing cycle</legend>
        <RadioGroup class="morphos-radio-group" orientation="vertical" aria-label="Billing cycle">
          <Radio group={this.group} value="monthly" class="morphos-radio">Monthly</Radio>
          <Radio group={this.group} value="quarterly" class="morphos-radio">Quarterly</Radio>
          <Radio group={this.group} value="annually" class="morphos-radio">Annually</Radio>
        </RadioGroup>
      </fieldset>
    )
  }
}

Each Radio reads selection state from the group prop and calls group.select(value) on change — it must be given the same RadioGroup instance backing the rendered <RadioGroup> root so the two stay in sync.

Controlled

@Component()
class MyComponent extends StatefulComponent {
  @State() group = new RadioGroup({ orientation: 'vertical' })

  applyTheme(theme: string) {
    document.documentElement.dataset.theme = theme
  }

  render() {
    return (
      <fieldset>
        <legend>Theme</legend>
        <RadioGroup
          class="morphos-radio-group"
          value={() => this.group.selectedValue}
          orientation="vertical"
          onValueChange={(value) => this.applyTheme(value)}
        >
          <Radio group={this.group} value="light" class="morphos-radio">Light</Radio>
          <Radio group={this.group} value="dark" class="morphos-radio">Dark</Radio>
          <Radio group={this.group} value="system" class="morphos-radio">System</Radio>
        </RadioGroup>
      </fieldset>
    )
  }
}

Horizontal layout

@Component()
class MyComponent extends StatefulComponent {
  @State() group = new RadioGroup({ defaultValue: 's', orientation: 'horizontal' })

  render() {
    return (
      <fieldset>
        <legend>Size</legend>
        <RadioGroup class="morphos-radio-group" orientation="horizontal" aria-label="Size">
          <Radio group={this.group} value="s" class="morphos-radio">S</Radio>
          <Radio group={this.group} value="m" class="morphos-radio">M</Radio>
          <Radio group={this.group} value="l" class="morphos-radio">L</Radio>
          <Radio group={this.group} value="xl" class="morphos-radio">XL</Radio>
        </RadioGroup>
      </fieldset>
    )
  }
}

Props — RadioGroup

PropTypeDefaultDescription
valuestring—Controlled selected value
defaultValuestring—Uncontrolled initial selected value
namestring—Shared name attribute intended for radios in the group
disabledbooleanfalseDisables all radios in the group
requiredbooleanfalseMarks the group as required (aria-required)
orientation"vertical" | "horizontal""vertical"Sets aria-orientation and data-orientation
onValueChange(value: string) => void—Fires when the selected value changes
classstring—Additional CSS classes
idstring—HTML id attribute
childrenChildren—The Radio items
aria-labelstring—Accessible label when no <legend> is present
aria-labelledbystring—ID of an element that labels the group

Methods

MethodDescription
select(value: string)Sets the group's selected value. Emits onValueChange with the new value.

Props — Radio

PropTypeDefaultDescription
groupRadioGroup—The parent RadioGroup instance (required)
valuestring—The value this radio represents (required)
disabledboolean—Overrides the group's disabled for this radio only
childrenChildren—Label content rendered beside the input, inside the same <label>
classstring—Additional CSS classes on the <label>
idstring—HTML id attribute on the <label>
aria-labelstring—Accessible label for this individual radio's <input>
aria-labelledbystring—ID of an element that labels this individual radio's <input>

data-* attributes

AttributeElementWhen present
data-disabledGroup rootdisabled is true
data-orientationGroup rootAlways — value is "vertical" or "horizontal"
data-checkedRadio <label>This radio's value matches the group's selected value
data-disabledRadio <label>Group's disabled or this radio's own disabled is true

Accessibility

RadioGroup renders a <div role="radiogroup"> with aria-orientation, aria-required, and aria-disabled. Each Radio renders a native <input type="radio"> wrapped in a <label>; all radios sharing the same group reuse the name from RadioGroup.name. Wrap the group in a <fieldset> with a <legend> to provide context to assistive technologies — RadioGroup does not render its own <fieldset>.

RadioGroup and Radio do not implement roving-tabindex or arrow-key navigation — each radio input is independently focusable via Tab, and selection changes with the native change event on click. If you need custom keyboard navigation between options, wire it up yourself.

Styling example

.morphos-radio-group[data-orientation="horizontal"] {
  flex-direction: row;
  gap: var(--morphos-space-4);
}

.morphos-radio[data-checked] input[type="radio"] {
  border-color: var(--morphos-color-accent);
}

.morphos-radio[data-disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}

On this page