/Documentation

Radio buttons

PublishedUpdated

Radio buttons let people select one option from a set, when every option should be visible at once. Keep to five options or fewer, stacked vertically, with one selected by default; for more, use a select. See the M3 radio button guidelines.

Usage

Vanilla
import { createRadios } from 'mtrl';

const radios = createRadios({
  name: 'size',
  value: 'm',
  options: [
    { value: 's', label: 'Small' },
    { value: 'm', label: 'Medium' },
    { value: 'l', label: 'Large' },
  ],
});
radios.on('change', ({ value }) => setSize(value));
document.body.append(radios.element);
Web Components
<m-radios value="m" name="size">
  <m-radio value="s">Small</m-radio>
  <m-radio value="m">Medium</m-radio>
  <m-radio value="l">Large</m-radio>
</m-radios>

<script type="module">
  const radios = document.querySelector('m-radios');
  radios.addEventListener('change', (event) => setSize(event.detail.value));
</script>
React
import { useState } from 'react';
import { Radios, Radio } from 'mtrl/react';
import { setSize } from './app';

export function Example() {
  const [value, setValue] = useState("m");
  return (
    <Radios value={value} onChange={(event) => { setValue(event.detail.value); setSize(event.detail.value); }} name="size">
      <Radio value="s">Small</Radio>
      <Radio value="m">Medium</Radio>
      <Radio value="l">Large</Radio>
    </Radios>
  );
}
Vue
<script setup lang="ts">
import { ref } from 'vue';
import { MRadios, MRadio } from 'mtrl/vue';
import { setSize } from './app';

const value = ref("m");
</script>

<template>
  <MRadios v-model="value" @change="setSize($event.detail.value)" name="size">
    <MRadio value="s">Small</MRadio>
    <MRadio value="m">Medium</MRadio>
    <MRadio value="l">Large</MRadio>
  </MRadios>
</template>
Svelte
<script lang="ts">
  import { Radios, Radio } from 'mtrl/svelte';
  import { setSize } from './app';

  let value = $state("m");
</script>

<Radios bind:value onchange={(event) => setSize(event.detail.value)} name="size">
  <Radio value="s">Small</Radio>
  <Radio value="m">Medium</Radio>
  <Radio value="l">Large</Radio>
</Radios>
SolidJS
import { createSignal } from 'solid-js';
import { Radios, Radio } from 'mtrl/solid';
import { setSize } from './app';

export function Example() {
  const [value, setValue] = createSignal("m");
  return (
    <Radios value={value()} onChange={(event) => { setValue(event.detail.value); setSize(event.detail.value); }} name="size">
      <Radio value="s">Small</Radio>
      <Radio value="m">Medium</Radio>
      <Radio value="l">Large</Radio>
    </Radios>
  );
}

Examples

In a row, one option disabled

direction: 'horizontal' lays the options out in a row. An option's own disabled leaves the rest of the group usable.

Vanilla
import { createRadios } from 'mtrl';

const radios = createRadios({
  name: 'delivery',
  value: 'standard',
  direction: 'horizontal',
  options: [
    { value: 'standard', label: 'Standard' },
    { value: 'express', label: 'Express', disabled: true },
    { value: 'pickup', label: 'Pick up' },
  ],
});
document.body.append(radios.element);
Web Components
<m-radios value="standard" name="delivery" direction="horizontal">
  <m-radio value="standard">Standard</m-radio>
  <m-radio value="express" disabled>Express</m-radio>
  <m-radio value="pickup">Pick up</m-radio>
</m-radios>
React
import { useState } from 'react';
import { Radios, Radio } from 'mtrl/react';

export function Example() {
  const [value, setValue] = useState("standard");
  return (
    <Radios value={value} onChange={(event) => setValue(event.detail.value)} name="delivery" direction="horizontal">
      <Radio value="standard">Standard</Radio>
      <Radio value="express" disabled>Express</Radio>
      <Radio value="pickup">Pick up</Radio>
    </Radios>
  );
}
Vue
<script setup lang="ts">
import { ref } from 'vue';
import { MRadios, MRadio } from 'mtrl/vue';

const value = ref("standard");
</script>

<template>
  <MRadios v-model="value" name="delivery" direction="horizontal">
    <MRadio value="standard">Standard</MRadio>
    <MRadio value="express" disabled>Express</MRadio>
    <MRadio value="pickup">Pick up</MRadio>
  </MRadios>
</template>
Svelte
<script lang="ts">
  import { Radios, Radio } from 'mtrl/svelte';

  let value = $state("standard");
</script>

<Radios bind:value name="delivery" direction="horizontal">
  <Radio value="standard">Standard</Radio>
  <Radio value="express" disabled>Express</Radio>
  <Radio value="pickup">Pick up</Radio>
</Radios>
SolidJS
import { createSignal } from 'solid-js';
import { Radios, Radio } from 'mtrl/solid';

export function Example() {
  const [value, setValue] = createSignal("standard");
  return (
    <Radios value={value()} onChange={(event) => setValue(event.detail.value)} name="delivery" direction="horizontal">
      <Radio value="standard">Standard</Radio>
      <Radio value="express" disabled>Express</Radio>
      <Radio value="pickup">Pick up</Radio>
    </Radios>
  );
}

Setting the value

An action selects an option by its value. An unknown value clears the selection and emits change, with value '' and option null.

Vanilla
import { createRadios } from 'mtrl';

const radios = createRadios({
  name: 'size',
  value: 'm',
  options: [
    { value: 's', label: 'Small' },
    { value: 'm', label: 'Medium' },
    { value: 'l', label: 'Large' },
  ],
});
document.body.append(radios.element);

function reset() {
  radios.setValue('s');
}
Web Components
<m-radios value="m" name="size">
  <m-radio value="s">Small</m-radio>
  <m-radio value="m">Medium</m-radio>
  <m-radio value="l">Large</m-radio>
</m-radios>

<script type="module">
  const radios = document.querySelector('m-radios');

  function reset() {
    radios.value = "s";
  }
</script>
React
import { useState } from 'react';
import { Radios, Radio } from 'mtrl/react';

export function Example() {
  const [value, setValue] = useState("m");
  const reset = () => setValue("s");

  return (
    <Radios value={value} onChange={(event) => setValue(event.detail.value)} name="size">
      <Radio value="s">Small</Radio>
      <Radio value="m">Medium</Radio>
      <Radio value="l">Large</Radio>
    </Radios>
  );
}
Vue
<script setup lang="ts">
import { ref } from 'vue';
import { MRadios, MRadio } from 'mtrl/vue';

const value = ref("m");

function reset() {
  value.value = "s";
}
</script>

<template>
  <MRadios v-model="value" name="size">
    <MRadio value="s">Small</MRadio>
    <MRadio value="m">Medium</MRadio>
    <MRadio value="l">Large</MRadio>
  </MRadios>
</template>
Svelte
<script lang="ts">
  import { Radios, Radio } from 'mtrl/svelte';

  let value = $state("m");

  function reset() {
    value = "s";
  }
</script>

<Radios bind:value name="size">
  <Radio value="s">Small</Radio>
  <Radio value="m">Medium</Radio>
  <Radio value="l">Large</Radio>
</Radios>
SolidJS
import { createSignal } from 'solid-js';
import { Radios, Radio } from 'mtrl/solid';

export function Example() {
  const [value, setValue] = createSignal("m");
  const reset = () => setValue("s");

  return (
    <Radios value={value()} onChange={(event) => setValue(event.detail.value)} name="size">
      <Radio value="s">Small</Radio>
      <Radio value="m">Medium</Radio>
      <Radio value="l">Large</Radio>
    </Radios>
  );
}

An option's labelBefore puts its label before the radio, in the reading direction.

API

Options

Option Type Default Description
name string required The inputs' shared name, which makes them one group for the keyboard and forms; an empty name is replaced by a generated one
options RadioOptionConfig[] [] The options, in order
value string undefined The selected value
direction 'vertical' | 'horizontal' 'vertical' How the options are laid out
disabled boolean false Whether the whole group starts disabled
ripple boolean true Whether each radio has its state layer
class string undefined Additional CSS classes
prefix string 'mtrl' Prefix for CSS class names
rippleConfig object undefined Deprecated, no effect

Option

Option Type Default Description
value string required The option's value
label string required Its label; a click on it selects the option
disabled boolean false Whether the option starts disabled
labelBefore boolean false Places the label before the radio, in the reading direction

Methods

Method Parameters Returns Description
getValue() none string The selected value, or ''
setValue(value) value: string RadiosComponent Selects an option; an unknown value clears the selection and emits change
getSelected() none RadioOptionConfig | null The selected option
addOption(option) option: RadioOptionConfig RadiosComponent Adds an option
removeOption(value) value: string RadiosComponent Removes an option
enable() / disable() none RadiosComponent The whole group
enableOption(value) / disableOption(value) value: string RadiosComponent One option
on(event, handler) / off(event, handler) event: 'change', handler: Function RadiosComponent Adds or removes a listener
destroy() none void Removes the group

Events

Event Description Data
change The user selected an option, or an unknown value cleared the selection { value, option, originalEvent }

originalEvent is the DOM event of a user's selection, undefined otherwise. The web component's change carries { value }.

Accessibility

  • A radiogroup of native radio inputs, each named by its label. Name the group: the factory's element takes aria-label or aria-labelledby, the web component an aria-label attribute.
  • Tab lands on the selected radio, or the first; the arrow keys move and select, wrapping, and follow the reading direction in right-to-left layouts; Space selects the focused radio. This is the native behavior of radios sharing a name.
  • Keyboard focus draws a 0.10 state layer and a 3dp focus ring; a pointer shows no ring.

Styling

.mtrl-radios { }                   /* the group */
.mtrl-radios--vertical, .mtrl-radios--horizontal { }
.mtrl-radios__item { }
.mtrl-radios__input { }            /* the native radio */
.mtrl-radios__label, .mtrl-radios__label--before { }
.mtrl-radios__control { }          /* the 40dp state layer area */
.mtrl-radios__ripple { }           /* the state layer */
.mtrl-radios__circle { }           /* the ring; ::after is the dot */
.mtrl-radios__text { }

Measurements

From the m3.material.io radio button specs, then Compose's RadioButtonTokens and material-web.

Attribute Value
Icon 20dp, a 2dp ring
Unselected on-surface-variant ring (on-surface on hover, focus and press)
Selected primary ring and a 10dp primary dot
Disabled on-surface 38%
State layer 40dp circle: on-surface when unselected, primary when selected; a press takes the color of the state it leads to
Item 48dp tall
Label Body Medium, on-surface, 8dp from the control
Motion The dot springs in on the fast spatial spring