Askr documentation
UI & Components

Radio Group

Radio Group: anatomy, keyboard behavior, state, and theming in Askr.

Example

import { FieldLabel, RadioGroup, RadioGroupItem } from '@askrjs/themes/components';

<RadioGroup value={plan()} onValueChange={setPlan}>
  <FieldLabel><RadioGroupItem value="team" /> Team</FieldLabel>
  <FieldLabel><RadioGroupItem value="business" /> Business</FieldLabel>
</RadioGroup>
Try Radio Group

Choose exactly one option from a group.

Published props

Generated from the TypeScript declarations shipped by the installed package. Named types in the Type column define the accepted values.

RadioGroupItemAsChildProps

Import from @askrjs/ui/radio-group.

PropTypeDefaultDescription
asChildasChild: true;
childrenchildren: JSXElement;
disableddisabled?: boolean | undefined;
refref?: Ref<Element>;
valuevalue: string;

RadioGroupItemOwnProps

Import from @askrjs/ui/radio-group.

PropTypeDefaultDescription
childrenchildren?: unknown;
disableddisabled?: boolean | undefined;
valuevalue: string;

RadioGroupItemProps

Import from @askrjs/ui/radio-group.

PropTypeDefaultDescription
asChildasChild?: false | undefined;
childrenchildren?: unknown;
refref?: Ref<HTMLButtonElement>;
valuevalue: string;

RadioGroupOwnProps

Import from @askrjs/ui/radio-group.

PropTypeDefaultDescription
childrenchildren?: unknown;Supports literal, nested, array-mapped, and `For`-rendered descendants.
defaultValuedefaultValue?: string | undefined;
disableddisabled?: boolean | undefined;
looploop?: boolean | undefined;
namename?: string | undefined;
onValueChangeonValueChange?: ((value: string) => void) | undefined;
orientationorientation?: Orientation | undefined;
valuevalue?: string | undefined;

RadioGroupProps

Import from @askrjs/ui/radio-group.

PropTypeDefaultDescription
childrenchildren?: unknown;Supports literal, nested, array-mapped, and `For`-rendered descendants.
defaultValuedefaultValue?: string | undefined;
disableddisabled?: boolean | undefined;
looploop?: boolean | undefined;
namename?: string | undefined;
onValueChangeonValueChange?: ((value: string) => void) | undefined;
orientationorientation?: Orientation | undefined;
refref?: Ref<HTMLDivElement>;
valuevalue?: string | undefined;

Purpose

Choosing exactly one value from a set is what RadioGroup and RadioGroupItem are built for together, with the former acting as container and the latter representing each option. Unlike a collection of independent Checkboxes, this pairing means the container owns the shared value and coordinates which single item is selected. The result mirrors the roving-focus behavior users already expect from native radio buttons.

Install and import

Getting Radio Group running requires @askrjs/ui and @askrjs/askr for the headless primitives, plus @askrjs/themes if default styling matters to you. Both `RadioGroup` and `RadioGroupItem` are available headless from `@askrjs/ui/radio-group`, or pre-styled from `@askrjs/themes/radio-group` using the same two names. Themes re-exports both components straight from @askrjs/ui rather than wrapping them, so the styled and headless versions behave identically.

Live examples

A basic vertical group with three or four options is the starting point; from there, try a horizontal orientation, a disabled item mixed in with enabled ones, and a fully disabled group. An asChild example on a RadioGroupItem is also worth checking since, unlike RadioGroup's container, items support polymorphic rendering.

Anatomy

RadioGroup renders a single <div role="radiogroup"> wrapping any number of RadioGroupItem children; each RadioGroupItem defaults to a native <button> (not an <input>) with role="radio", which is why RadioGroupItemProps omits onClick, type, and value from the native button attributes it otherwise passes through. The group also renders a hidden native input for form integration, per FORM_INTEGRATION.hiddenInputType in its accessibility contract, so the selected value still participates in a plain HTML form submission.

State model

The selected value lives on RadioGroup itself — value + onValueChange for controlled use, or defaultValue for uncontrolled — and each RadioGroupItem only declares the value it represents plus its own disabled flag. Individual items don't track checked state locally; whether an item renders as selected is derived by comparing its value prop against the group's current value.

Keyboard and accessibility

RadioGroup implements roving focus: the selected item (or the first enabled one) has tabIndex 0 and the rest have tabIndex -1, and ArrowLeft/ArrowRight/ArrowUp/ArrowDown move selection between items, matching the WAI-ARIA radio group pattern. The container carries role="radiogroup" and aria-orientation, while each item carries role="radio" and aria-checked, and the whole group can be set to loop or stop at the ends via the loop prop.

Styling and tokens

The default theme styles [data-slot="radio-group"] as a responsive grid (auto-fit columns when horizontal) and renders each [data-slot="radio-group-item"] as a full card-style row — border, padding, and a hand-drawn radio dot appended via ::before — rather than a bare native radio circle. That means the themed radio group looks more like a selectable list than a classic row of small circles; data-orientation controls whether items lay out as a single column or a wrapping grid.

API

RadioGroupOwnProps covers value?: string, defaultValue?: string, onValueChange?: (value: string) => void, disabled?: boolean, name?: string, orientation?: Orientation, and loop?: boolean, layered onto native <div> attributes for RadioGroupProps. RadioGroupItemOwnProps is just value: string (required), disabled?: boolean, and children, with RadioGroupItemProps building on native <button> attributes and RadioGroupItemAsChildProps swapping in asChild: true with a single child.

Edge cases

Because each RadioGroupItem is a <button> by default rather than an <input type="radio">, you can't rely on native form serialization from the visible items alone — that's exactly why the group renders its own hidden input to carry the value into a form submit. Disabling the group's own disabled prop cascades to every item, but disabling one RadioGroupItem individually while leaving the group and its siblings enabled works too — check which level you actually want before setting the prop.

See Checkbox for independent (non-exclusive) toggles, Select for choosing one value from a larger or less immediately visible list, and Form, Label, Field, and Input Group for grouping RadioGroup under a shared legend and description.