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>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.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
disabled | disabled?: boolean | undefined; | — | — |
ref | ref?: Ref<Element>; | — | — |
value | value: string; | — | — |
RadioGroupItemOwnProps
Import from @askrjs/ui/radio-group.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | — |
disabled | disabled?: boolean | undefined; | — | — |
value | value: string; | — | — |
RadioGroupItemProps
Import from @askrjs/ui/radio-group.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
ref | ref?: Ref<HTMLButtonElement>; | — | — |
value | value: string; | — | — |
RadioGroupOwnProps
Import from @askrjs/ui/radio-group.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | Supports literal, nested, array-mapped, and `For`-rendered descendants. |
defaultValue | defaultValue?: string | undefined; | — | — |
disabled | disabled?: boolean | undefined; | — | — |
loop | loop?: boolean | undefined; | — | — |
name | name?: string | undefined; | — | — |
onValueChange | onValueChange?: ((value: string) => void) | undefined; | — | — |
orientation | orientation?: Orientation | undefined; | — | — |
value | value?: string | undefined; | — | — |
RadioGroupProps
Import from @askrjs/ui/radio-group.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | Supports literal, nested, array-mapped, and `For`-rendered descendants. |
defaultValue | defaultValue?: string | undefined; | — | — |
disabled | disabled?: boolean | undefined; | — | — |
loop | loop?: boolean | undefined; | — | — |
name | name?: string | undefined; | — | — |
onValueChange | onValueChange?: ((value: string) => void) | undefined; | — | — |
orientation | orientation?: Orientation | undefined; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
value | value?: 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.
Related pages
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.