RadioButton

Updated: Sep 24, 2026
RadioButton draws the selected or unselected state of one option in a mutually exclusive group: a circular track with a stroke ring, filled with an inner circle when selected. It is a visual building block, not an input. It has no change callback, takes no focus, and never modifies its own state — the parent that owns the selected value passes checked down.
For a real choice list, do not compose RadioButton yourself. Use ListItem with showRadioButton inside a VerticalList, so each option is one labelled focus target and the group has a single source of truth. ListItem also owns the radio role and state, so a row is announced as one widget instead of a widget containing another widget.

Usage

import { useState } from 'react';
import { ListItem, VerticalList } from '@wearables-ui-toolkit/mrbd';

const QUALITY_OPTIONS = ['Standard', 'High', 'Maximum'];

export function CaptureQuality() {
  const [selected, setSelected] = useState('High');

  return (
    <VerticalList>
      {QUALITY_OPTIONS.map(option => (
        <ListItem
          key={option}
          title={option}
          showRadioButton
          checked={option === selected}
          onCheckedChange={() => setSelected(option)}
        />
      ))}
    </VerticalList>
  );
}
Use RadioButton directly only where some other, clearly labelled control already owns the choice and you just need the glyph:
import { RadioButton, TextView, TextStyle } from '@wearables-ui-toolkit/mrbd';

export function SelectedQualitySummary({ quality }: { quality: string }) {
  return (
    <div className="quality-summary">
      <RadioButton checked aria-label={quality} />
      <TextView textStyle={TextStyle.BODY2}>{quality}</TextView>
    </div>
  );
}

Props

The full prop type is RadioButtonProps.
PropTypeDefaultDescription
checked
boolean
false
Whether the inner fill is drawn. Fully controlled; the component never changes it.
RadioButtonProps also accepts the standard div attribute surface — className, style, aria-label, title, data-* (including data-uit-*), and DOM event handlers — which is spread onto the root element. There is no onChange prop (it is removed from the accepted attributes), no disabled prop, and no animation prop.
The component supplies its own role, aria-checked, aria-disabled, aria-hidden, tabIndex, onClick, and onKeyDown after the rest props, so none of those can be overridden: an onClick or onKeyDown you pass to RadioButton is never attached, and an aria-hidden or aria-disabled you pass is dropped. Other handlers, such as onFocus and onPointerEnter, do reach the root element.

Behavior

  • Controlled only.checked is the single source of truth. Clicking the glyph does nothing; wire activation on the parent.
  • Rendering. An inline SVG draws a filled background circle and a stroke ring; when checked is true a third, inner filled circle is added. The background uses --uit-color-background-inset, and the ring and inner fill use --uit-color-control-active.
  • No transition. Selecting and deselecting swap the inner circle in and out immediately. There is no state animation and nothing to configure.
  • Sizing. The root is a fixed-size inline-flex square written as inline styles, and it does not shrink in a flex row. Your style object is merged after those, so let the parent lay the glyph out rather than resizing it.
  • No disabled treatment. There is no disabled prop and no dimmed variant. Express unavailability on the parent row.
  • Ref. The ref forwards to the root div, which is all you need for measuring or anchoring something to the glyph.

Accessibility

  • The component reports role="radio" with aria-checked reflecting checked.
  • It is not focusable and not operable: there is no tab stop, no key handling, and the root keeps the default cursor. The accessible action belongs to the parent.
  • It is never hidden from assistive technology, and aria-hidden cannot be turned on from the outside, so a RadioButton nested inside another control is always announced as an extra widget.
  • Give it an aria-label when nothing adjacent names the option.
  • Do not place it inside an element that already reports radio semantics; that produces a duplicate nested widget. Use ListItem with showRadioButton instead.
  • A set of options needs a name of its own. Label the group container, not only the individual rows.
  • Use radio semantics only for a mutually exclusive set with exactly one selected value.
  • Keep the whole group driven by one state value, and derive each row’s checked from it.
  • Label the group itself, not only the individual options.
  • Keep the option list short enough to scan without hunting for the selection.

Avoid

  • Avoid using radio buttons for independent settings; use Switch through ListItem.
  • Avoid leaving a group with zero or more than one selected option.
  • Avoid attaching your own click or key handler to the glyph — it will be dropped, and the option will look interactive without being operable.
  • Avoid wrapping a RadioButton in your own focusable element to make it selectable; that announces two nested widgets.
  • Use the component-owned ring and fill. Internal DOM structure and generated class names are not supported customization surfaces.