Pho Design System

Radio Group

Chooses exactly one option from a small, visible set. Built on Base UI RadioGroup + Radio, with arrow-key navigation between options. When the set is long or space is tight, prefer Select.

Import

import {
  RadioGroup,
  Radio,
  RadioCard,
} from "@photon-ai/pho-ui/components/radio-group";

Basic

Each Radio needs a value; label makes the whole row selectable. Leave the group uncontrolled with defaultValue.

<RadioGroup defaultValue="standard">
  <Radio value="standard" label="Standard — 3–5 business days" />
  <Radio value="express" label="Express — next business day" />
  <Radio value="overnight" label="Overnight" />
</RadioGroup>

Disabled

Set disabled on a single Radio to remove just that option, or on RadioGroup to disable every option at once. Disabled options are skipped by arrow-key navigation.

<Radio value="overnight" label="Overnight — not available" disabled />

Controlled

Drive the selection yourself with value and onValueChange when the choice feeds other state.

Selected: standard

const [value, setValue] = useState("standard");

<RadioGroup value={value} onValueChange={setValue}>
  <Radio value="standard" label="Standard" />
  <Radio value="express" label="Express" />
  <Radio value="overnight" label="Overnight" />
</RadioGroup>;

Card

RadioCard wraps the same Radio in a bordered, selectable tile — pass title and an optional description instead of label. Use it when each option carries enough weight (a plan, a brand type) to deserve more visual presence than a plain labeled dot. The whole tile is the hit target and carries the press feedback; arrange a set of them with RadioGroup as usual.

OrganizationA registered company or non-profit.Sole proprietorAn individual with lighter carrier vetting.
<RadioGroup defaultValue="org" className="flex flex-col gap-2 sm:flex-row">
  <RadioCard
    value="org"
    title="Organization"
    description="A registered company or non-profit."
  />
  <RadioCard
    value="sole"
    title="Sole proprietor"
    description="An individual with lighter carrier vetting."
  />
</RadioGroup>

Card with icon

Pass icon when the option has a glyph — it stacks above the title and replaces the radio dot.

OrganizationA registered company or non-profit.Sole proprietorAn individual with lighter carrier vetting.
<RadioGroup defaultValue="org" className="flex flex-col gap-2 sm:flex-row">
  <RadioCard
    value="org"
    title="Organization"
    description="A registered company or non-profit."
    icon={<IconBuilding />}
  />
  <RadioCard
    value="sole"
    title="Sole proprietor"
    description="An individual with lighter carrier vetting."
    icon={<IconUser />}
  />
</RadioGroup>

Card with a trailing glyph

A choice that adds something rather than picks it (a new brand beside the ones already registered) takes trailing: a glyph at the end of the title row, in the dot’s place. The tile’s ring still shows it chosen.

Acme Inc.Verified.New brandAdd one carriers haven't seen yet.
<RadioGroup defaultValue="acme" className="flex flex-col gap-2">
  <RadioCard value="acme" title="Acme Inc." description="Verified." />
  <RadioCard
    value="new"
    title="New brand"
    description="Add one carriers haven't seen yet."
    trailing={<IconPlus />}
  />
</RadioGroup>

Anatomy

Props

RadioGroup

Radio

RadioCard

Accessibility

RadioGroup renders role="radiogroup" and each Radio renders role="radio" with aria-checked. Passing label wraps the dot and text in a <label>, so the whole row is both clickable and the option’s accessible name; without a label, give the Radio an aria-label. required and readOnly surface as aria-required and aria-readonly on the group. RadioCard renders its title and description inside the radio tile, so they compose into the option’s accessible name.

The group is a single tab stop: roving tabindex keeps only one radio tabbable, so Tab moves past the group rather than through each option.

Best practices