Askr
UI & Components

Radio Group

Use radio group with the current published Askr packages.

  • @askrjs/ui/radio-group0.0.13
  • @askrjs/themes/radio-group0.0.13

How to use radio group

Radio Group should use the standard themed surface unless the application is deliberately implementing its own design system on top of @askrjs/ui.

  1. Import the themed component from @askrjs/themes/components; use @askrjs/ui directly only when building a custom visual system.
  2. Keep radio group labels, state, and application actions visible at the call site instead of styling internal DOM nodes.
  3. Verify keyboard behavior, focus movement or return, disabled state, and both standard themes.
import { FieldLabel, RadioGroup, RadioGroupItem } from '@askrjs/themes/components';

<RadioGroup value={plan()} onValueChange={plan.set}>
  <FieldLabel><RadioGroupItem value="team" /> Team</FieldLabel>
  <FieldLabel><RadioGroupItem value="business" /> Business</FieldLabel>
</RadioGroup>

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.