Radio Group

A set of mutually exclusive options where exactly one can be selected, built on Radix RadioGroup. Sizes line up pixel-for-pixel with Checkbox, so radios and checkboxes sit flush in the same form.

Installation#

# One-time setup (tokens + lib helpers)npx koalaui-cli@latest init# Add this component (its dependencies come along)npx koalaui-cli@latest add radio-group

Usage#

usage.tsxTSX
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"export function Example() {  return (    <RadioGroup defaultValue="pro">      <label htmlFor="pro" className="flex items-center gap-2.5">        <RadioGroupItem id="pro" value="pro" />        Pro      </label>    </RadioGroup>  )}

Anatomy#

import {  RadioGroup,  RadioGroupItem,  RadioSwatch,  RadioCard,  RadioCardTitle,  RadioCardDescription,} from "@/components/ui/radio-group"<RadioGroup>  <RadioGroupItem />  <RadioSwatch />  <RadioCard>    <RadioCardTitle />    <RadioCardDescription />  </RadioCard></RadioGroup>

Sizes#

sm (16px) matches the dense table checkbox; md (20px) is the default for forms. Set size on the group and every item inherits it through context.

sm
md

States#

Disable a single RadioGroupItem and roving focus skips it, or disable the whole RadioGroup at once.

With descriptions#

Give each option a line of supporting text with RadioCardDescription. The whole card is the radio, so the entire surface is the hit target: selected, it takes an ink border and pops its icon, exactly like the plan picker below.

Delivery speed

Cards#

Reach for RadioCard when each option deserves an icon and a line of supporting text, the affordance behind a plan, theme or payment-method picker. The card is the radio, so keyboard and focus come for free; selected, it takes an ink border while the flooded dot tweens in. Reached by keyboard, it keeps the same ink and adds a soft halo, so colour only ever means selection.

Plan

Card sizes#

md (the default, above) suits a title plus a line of description. Drop a RadioCard to size="sm" when the option is a single line, a title on its own or an icon and text, so it doesn't float in dead space. The padding, control and radius all tighten together, the row centers vertically, and the title drops to medium; the control still lines up with the standalone radio.

Billing type
Payment method

Swatches#

RadioSwatch dresses a radio as a colour chip, the picker on a product page. The chip is filled with its color (any CSS colour: a product's colour is data, not a theme role), keeps a hairline edge so a pale colour never dissolves into the page, and takes a fine ink ring, cut off the chip by a gap in the ground's own colour, when chosen. The ring is ink rather than brand on purpose: the chip's colour is the content, so a brand ring would add a colour the product doesn't have. Keyboard focus is a separate brand outline outside it, so focus and selection never look alike. It has no visible text, so label is required: it is the name a screen reader announces. Show the chosen name in the legend for everyone else, and lay the group out as a row.

Color Sage

Swatch sizes#

Swatches follow the group's size: sm is 24px for a product card or a filter, md is 32px for a product page. Either way the hit target grows to 40px (44 on touch), so keep at least an 8px gap between chips. A disabled swatch fades and roving focus skips it; say why in its label ("Clay, sold out").

sm
md

Controlled#

Pass value and onValueChange to own the selection. The group lays out horizontally here with a grid-flow-col override on className.

Density
Density: comfortable

API reference#

RadioGroup

The group root. Wraps Radix RadioGroup.Root and forwards its props; inside a Field it picks up the field's disabled, label and description. Every part accepts className, merged last.

PropTypeDefaultDescription
size"sm" | "md""md"Shared with every RadioGroupItem (16px or 20px, matching Checkbox) and RadioSwatch (24px or 32px) through context.
valuestring-The selected value, controlled. Pair with onValueChange.
defaultValuestring-The initially selected value, uncontrolled.
onValueChange(value: string) => void-Called when the selection changes.
disabledbooleanfalseDisables every item. Falls back to the surrounding Field's disabled.
requiredbooleanfalseMarks the group required for form submission.
namestring-The field name submitted with a form.
orientation"horizontal" | "vertical"-Which arrow keys move focus; both axes when unset.
loopbooleantrueWhether keyboard focus wraps from the last item to the first.
Data attributeDescription
data-disabledPresent when the group is disabled.

RadioGroupItem

The bare circle control. Wraps Radix RadioGroup.Item (a <button>) and forwards its props; pair it with a <label htmlFor> for its name and a larger hit target.

PropTypeDefaultDescription
value*string-The value this item selects.
disabledbooleanfalseDisables this item; roving focus skips it.
Data attributeDescription
data-state"checked" or "unchecked".
data-disabledPresent when the item is disabled.

RadioSwatch

A RadioGroupItem dressed as a colour chip for a product variant picker; its size follows the group's size. Wraps Radix RadioGroup.Item and forwards its props except children.

PropTypeDefaultDescription
value*string-The value this swatch selects.
color*string-The chip's fill, any CSS colour, set inline.
label*string-The accessible name (e.g. "Navy"), since the chip has no visible text.
disabledbooleanfalseDisables this swatch.
Data attributeDescription
data-state"checked" or "unchecked".
data-disabledPresent when the swatch is disabled.

RadioCard

A whole selectable card that is the radio itself, with a trailing circle mirroring its state. Wraps Radix RadioGroup.Item and forwards its props; children go in the card body.

PropTypeDefaultDescription
value*string-The value this card selects.
iconReact.ReactNode-Optional leading glyph, muted until the card is selected, then full ink.
size"sm" | "md""md"Set per card, not inherited from the group. md top-aligns a title and description; sm is a tighter, vertically centered one-line option.
disabledbooleanfalseDisables this card.
Data attributeDescription
data-state"checked" or "unchecked".
data-disabledPresent when the card is disabled.

RadioCardTitle

The card's title line. Forwards all native <span> props (a <button> may only hold phrasing content).

RadioCardDescription

The card's supporting text under the title. Forwards all native <span> props.

FAQ#

Use RadioGroup for a single choice from a small, visible set so every option is in view. Reach for a Select once the options grow long enough to want a dropdown.