Morphos
Inputs

RadioGroup

A compound component for single-selection radio button groups.

Interactive example

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
valuestringControlled selected value
defaultValuestringUncontrolled initial selected value
namestringShared 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) => voidFires when the selected value changes
classstringAdditional CSS classes
idstringHTML id attribute
childrenChildrenThe Radio items
aria-labelstringAccessible label when no <legend> is present
aria-labelledbystringID of an element that labels the group

Props — Radio

PropTypeDefaultDescription
groupRadioGroupThe parent RadioGroup instance (required)
valuestringThe value this radio represents (required)
disabledbooleanOverrides the group's disabled for this radio only
childrenChildrenLabel content rendered beside the input, inside the same <label>
classstringAdditional CSS classes on the <label>
idstringHTML id attribute on the <label>
aria-labelstringAccessible label for this individual radio's <input>
aria-labelledbystringID 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