Radio Group

A radio group lets people pick exactly one option from a short list that stays visible.

A radio group shows every option at once and lets people choose one of them. When people can choose more than one, use a Checkbox group. A long list fits better in a Select, and switching between views of the same content is a Segmented Control.

Playground

Delivery

Usage

import { Flex, RadioGroup, Text } from '@kushagradhawan/kookie-ui';
 
export function DeliveryOptions() {
  return (
    <Flex direction="column" gap="3">
      <Text as="div" weight="medium" id="delivery-label">
        Delivery
      </Text>
      <RadioGroup.Root
        name="delivery"
        defaultValue="standard"
        aria-labelledby="delivery-label"
      >
        <RadioGroup.Item value="standard">Standard, 3 to 5 days</RadioGroup.Item>
        <RadioGroup.Item value="express">Express, next day</RadioGroup.Item>
        <RadioGroup.Item value="pickup">Pick up in store</RadioGroup.Item>
      </RadioGroup.Root>
    </Flex>
  );
}

Anatomy

<RadioGroup.Root>
  <RadioGroup.Item value="a">Label</RadioGroup.Item>
</RadioGroup.Root>
  • RadioGroup.Root: Holds the selected value and sets size, variant and color for every item.
  • RadioGroup.Item: One option. With children it renders a label around the circle and the text. Without children it renders the circle alone, for your own layout.

Radio is a separate component: a native input type="radio" with the same look, for forms that manage their own radio inputs.

Best practices

Use a radio group for two to about six options. People can compare every choice without opening anything. Past that, the list gets long to scan, and a Select takes less room.

Select a sensible default. A preselected option tells people what most of them choose and saves a step. Leave the group empty only when no answer is safe to assume, such as a question people must think about.

Give the group a visible question. "Delivery" above the options says what the choice is for. Point aria-labelledby at it so screen readers announce it too.

Keep labels short and parallel. Each label should answer the same question in the same form, so people compare options rather than sentences.

Don't use a radio group for an on and off setting. One option that is on or off is a Switch or a Checkbox. A pair of radios for "On" and "Off" doubles the reading for one decision.

Size

size picks the circle: 16, 20, 24 and 26 pixels for sizes 1 to 4. Size 2 is the default. The label text in a group follows the same size, and the circle takes the line height of that text, so it lines up with the first line of the label.

Examples

Options with a description

Pass your own layout as children. The whole block becomes the label, so a click anywhere on it selects the option.

<RadioGroup.Root defaultValue="monthly" aria-label="Billing period">
  <RadioGroup.Item value="monthly">
    <Flex direction="column">
      <Text weight="medium">Monthly</Text>
      <Text emphasis="medium">Billed every month, cancel any time</Text>
    </Flex>
  </RadioGroup.Item>
  <RadioGroup.Item value="yearly">
    <Flex direction="column">
      <Text weight="medium">Yearly</Text>
      <Text emphasis="medium">Two months free, billed once a year</Text>
    </Flex>
  </RadioGroup.Item>
</RadioGroup.Root>

Required answer

Leave out defaultValue and set required when people must make a choice. The form won't submit until one option is selected.

<RadioGroup.Root name="role" required aria-labelledby="role-label">
  <RadioGroup.Item value="admin">Admin</RadioGroup.Item>
  <RadioGroup.Item value="member">Member</RadioGroup.Item>
</RadioGroup.Root>

Native radio inputs

Radio renders a real input type="radio". Inputs with the same name form a group, as in plain HTML. onValueChange fires with the input's value when it is selected.

<Text as="label" size="2">
  <Flex gap="2">
    <Radio name="plan" value="free" defaultChecked />
    Free
  </Flex>
</Text>

Props

RadioGroup.Root

PropTypeDefaultDescription
valuestringThe selected value, controlled
defaultValuestringThe selected value at first render
onValueChange(value: string) => voidFires with the new value
size'1' | '2' | '3' | '4', responsive'2'The circle and label size
variant'raised' | 'solid' | 'soft' | 'outline' | 'surface''raised'The look of every item
colorAccentColorTheme accentThe colour of every item
namestringThe field name in a form
disabledbooleanfalseDisables every item
requiredbooleanfalseAn option must be selected before the form submits
orientation'horizontal' | 'vertical'Which arrow keys move between items. Both work when unset
loopbooleantrueArrow keys wrap from the last item to the first
dir'ltr' | 'rtl'The reading direction for arrow keys
asChildbooleanfalseRenders the child element as the group

RadioGroup.Root also accepts every attribute of the HTML div element.

RadioGroup.Item

PropTypeDefaultDescription
valuestringRequired. The value this option selects
disabledbooleanfalseDisables this option

RadioGroup.Item also accepts every attribute of the HTML button element.

Radio

PropTypeDefaultDescription
valuestringRequired. The value the input submits
onValueChange(value: string) => voidFires with value when the input is selected
size'1' | '2' | '3' | '4', responsive'2'The circle size
variant'raised' | 'solid' | 'soft' | 'outline' | 'surface''raised'The look
colorAccentColorTheme accentThe colour when selected

Radio also accepts every attribute of the HTML input element that applies to radio inputs, such as name, checked, defaultChecked, disabled and required.

Accessibility

KeyAction
TabMoves focus into the group, onto the selected option, and out again
Arrow keysMove to the next or previous option and select it
SpaceSelects the focused option
  • Name: Name the group with aria-labelledby or aria-label. Each item takes its name from its label text. An item without children needs its own aria-label.
  • State: The root has role="radiogroup" and each item has role="radio". A screen reader announces the option, whether it is selected, and its position in the group.
  • Focus: The group is one tab stop. Focus enters on the selected option, or on the first option when none is selected.
  • Radio: Radio is a native input, so the browser handles its keyboard and announcements. Wrap it in a label so the text names it.