Skip to content
TopClass UI

Radio group

One choice out of a few, all shown at once. One Tab stop for the group, and the checked radio is the tab stop before hydration.

client - size not measured yet

Radio group

client - size not measured yet

One choice out of a few, all shown at once. One Tab stop for the group, and the checked radio is the tab stop before hydration.

Install

Try it

size
orientation
invalid

The code

radio-group.tsx
import { RadioGroupItem, RadioGroupRoot } from 'topclassui/radio-group'

<div className="grid justify-items-start gap-2">
  <span id="radio-group-playground-label" className="text-sm text-body">Billing</span>
  <RadioGroupRoot aria-labelledby="radio-group-playground-label" defaultValue="yearly">
    <RadioGroupItem value="monthly">Monthly</RadioGroupItem>
    <RadioGroupItem value="yearly">Yearly</RadioGroupItem>
    <RadioGroupItem value="lifetime">Lifetime</RadioGroupItem>
  </RadioGroupRoot>
</div>

The group carries the state and the form; each RadioGroupItem carries only its value and its text.

Every state

4 states

Import from the package

topclassui/radio-group
import { RadioGroupRoot, RadioGroupItem } from 'topclassui/radio-group'

Install once:npm i topclassui@beta zod

Examples

  • For one choice out of a few. For many options, a Select reads better; for two to five short ones in a bar, a SegmentedControl.
  • In a form the group sends the checked value, and nothing while no radio is checked. Before the page hydrates it sends the default.
  • orientation is vertical (a column) or horizontal (a row that wraps). It is layout only: the arrow keys are the same in both.
  • A long text wraps under itself rather than ending in an ellipsis: a choice is read whole.
  • On an item, style goes to the <label>; ref goes to the radio button itself.
basic
Billing
horizontal
form
Plan
sizes
long-content
In Arabic, right to left
الفوترة

Props

RadioGroupRoot

The props of RadioGroupRoot
PropTypeDefaultLimitsDescription
aria-labelstring--The name read out when no visible text names the group.
aria-labelledbystring--The id of the visible text that names the group.
invalidbooleanfalse-RadioGroupRoot: marks the choice as wrong: sets aria-invalid on the group, and every circle turns to danger and thickens.
orientationvertical | horizontal"vertical"-RadioGroupRoot: a column, or a row that wraps. Layout only, written as data-orientation; the arrow keys do not change.
refRef<HTMLDivElement>--The element with role="radiogroup".
sizexs | sm | md | lg | xl | custom"md"-RadioGroupRoot: the toggle scale of every circle and the type step of its label; custom emits no size classes.

RadioGroupItem

The props of RadioGroupItem
PropTypeDefaultLimitsDescription
value*string--The string the form receives, and what value and onValueChange of the group carry.
refRef<HTMLButtonElement>--The radio itself, which is a <button>.
styleCSSProperties--On the <label> around the radio and its text.

Keyboard and accessibility

The keyboard of RadioGroup
KeysWhat it does
TabEnters the group on the checked radio, and leaves it on the next Tab.
ArrowDownArrowRightMove to the next radio and select it, skipping a disabled one and wrapping at the end.
ArrowUpArrowLeftMove to the previous radio and select it, wrapping at the start.
SpaceSelects the focused radio. Enter does not.
  • Name the group with aria-label or aria-labelledby: no <label htmlFor> can name a group. Each radio is named by its own text.
  • The target is 24px, 44px under a touch pointer, whatever size draws: the focus ring is around the circle.
  • invalid sets aria-invalid on the group: every circle turns to danger and its line thickens, checked or not.

Right to left

Each circle sits at the right, before its text. Under a right-to-left TopClassProvider, ArrowRight goes to the radio before.

Server and client

A client component: it renders on the server and hydrates in the browser.

Files: index.tsx, client.tsx.

On the chip: client - size not measured yet.