RadioGroup
Accessible radio group for single-option selection with semantic intents, keyboard navigation, and form integration.
Playground
<RadioGroup
intent="primary"
size="md"
variant="outlined"
>
<RadioItem value="all" label="All notifications" />
<RadioItem value="mentions" label="Mentions only" />
<RadioItem value="none" label="None" />
</RadioGroup>01 Examples
Plan picker with descriptions
Selected: pro
<div class="border-border-subtle bg-surface-elevated w-full space-y-4 rounded-2xl border p-5">
<RadioGroup label="Choose your plan" bind:value={selectedPlan} intent="success">
<RadioItem
value="free"
label="Free"
description="3 projects, 1 GB storage, community support"
/>
<RadioItem
value="pro"
label="Pro — $12/mo"
description="Unlimited projects, 100 GB, priority support"
/>
<RadioItem
value="enterprise"
label="Enterprise"
description="Custom limits, SLA, dedicated account manager"
/>
</RadioGroup>
<p class="text-text-tertiary text-xs">
Selected: <span class="text-text-primary font-medium">{selectedPlan}</span>
</p>
</div>Per-item disabled
<RadioGroup label="Shipping speed" value="standard">
<RadioItem value="standard" label="Standard (3-5 days)" />
<RadioItem value="express" label="Express (1-2 days)" disabled />
<RadioItem value="overnight" label="Overnight" />
</RadioGroup>Helper & error
error overrides helper when both are set.<RadioGroup label="Frequency" helper="You can change this later in settings">
<RadioItem value="daily" label="Daily" />
<RadioItem value="weekly" label="Weekly" />
</RadioGroup>
<RadioGroup label="Agreement" error="Please select an option to continue">
<RadioItem value="accept" label="I accept" />
<RadioItem value="decline" label="I decline" />
</RadioGroup>Theme Selector
<div class="border-border-subtle bg-surface-elevated rounded-2xl border p-5">
<RadioGroup
label="Appearance"
orientation="horizontal"
bind:value={selectedTheme}
intent="neutral"
>
<RadioItem value="light" label="Light" />
<RadioItem value="dark" label="Dark" />
<RadioItem value="system" label="System" />
</RadioGroup>
</div>02 Customization
Card Options via slotClasses
<RadioGroup label="Region" value="eu" slotClasses={{ group: 'gap-2' }}>
<RadioItem
value="eu"
label="EU (Frankfurt)"
description="GDPR-friendly, lowest latency in Europe"
slotClasses={{
item: 'w-full rounded-xl border border-border-subtle p-3 has-[:checked]:border-primary has-[:checked]:bg-primary/5'
}}
/>
<RadioItem
value="us"
label="US (Virginia)"
description="Closest to North American users"
slotClasses={{
item: 'w-full rounded-xl border border-border-subtle p-3 has-[:checked]:border-primary has-[:checked]:bg-primary/5'
}}
/>
</RadioGroup>unstyled (per item) drops the indicator and label
styling while the native radio semantics, roving tabindex, and arrow-key navigation keep
working. When the card treatment above is your app's standard option style, register it once
as a BlocksProvider preset (presets.RadioGroup / presets.RadioItem) instead of repeating slotClasses — see Customization.
03 Accessibility
Native Semantics
Built on native <input type="radio"> elements inside a role="radiogroup" container for
correct form behavior and assistive technology support. The group label is linked via aria-labelledby, and error/helper text via aria-describedby.
Keyboard
Tab into the group focuses the selected (or first) item. Arrow keys move between options and select automatically. Vertical groups use Up/Down, horizontal groups use Left/Right. Navigation wraps around at both ends.
Focus Management
Uses roving tabindex: only the selected item (or the first item when nothing is selected) is
in the tab order. Disabled items are skipped during arrow key navigation. Focus rings use focus-visible: via the peer pattern for keyboard-only visibility.
Reduced Motion
All Mint effects and transitions respect prefers-reduced-motion. The dot transition is purely
visual and does not affect interaction.
04 API Reference
Prop | Type | Default | Description | |
|---|---|---|---|---|
children required | Snippet | — | RadioItem children to render inside the group. | |
class | string | — | Extra classes merged onto the root wrapper element. | |
disabled | boolean | — | Disable all radio items in the group. | |
error | string | — | Error message below the group. Replaces helper and sets aria-invalid. | |
helper | string | — | Hint text below the group. Hidden when error is set. | |
id | string | — | Explicit id for the group element. Auto-generated if omitted. | |
intent | RadioItemVariants['intent'] | 'primary' | Semantic color propagated to all child RadioItems. | |
label | string | — | Group label displayed above the radio items. | |
mint | MintProp | 'none' | Micro-interaction preset applied to each RadioItem (per-item via context). Only applies while the item is not disabled. | |
name | string | — | Shared name attribute for all radio inputs. Auto-generated if omitted. | |
onValueChange | (value: string) => void | — | Fired after the selected value changes. Receives the new value. | |
orientation | horizontalvertical | 'vertical' | Stack direction for the radio items. | |
preset | string | — | Apply a named preset registered via <BlocksProvider presets={{ RadioGroup: {...} }}>.
Prefer this over class overrides when the requested look falls outside the
semantic intent palette — presets keep hover/active/dark-mode logic coherent
and make the custom look reusable across the project. | |
required | boolean | — | Mark the group as required for form validation. Adds an asterisk to the label. | |
size | RadioItemVariants['size'] | 'md' | Visual size propagated to all child RadioItems. | |
slotClasses | Partial<Record<RadioGroupSlots, string>> | — | Per-slot class overrides merged with tv() styles. | |
tier | InteractiveTier | 'commit' | Semantic radius tier propagated to every RadioItem. Default commit
— radio indicators read as identity circles. Set to modify (or
inherit via TierContext from a wrapping <Toolbar tier="modify">)
for inline-toolbar contexts where a circle feels oversized. | |
unstyled | boolean | — | Remove all default tv() classes. Only user-provided classes apply. | |
value | string | — | Currently selected value. Supports two-way binding via bind:value. | |
variant | RadioItemVariants['variant'] | 'outlined' | Visual weight propagated to all child RadioItems. | |
...HTMLAttributes<HTMLDivElement> inherited | HTMLAttributes | — | HTML attributes (excluding: 'children' | 'class') |
05 Types
Local type definitions used by this component.
Name | Kind | Category | Used by | Description | |
|---|---|---|---|---|---|
RadioGroupProps | interface | props | 0 | — | |
RadioGroupContext | interface | helper | 0 | Reactive context exposed to child RadioItem components via getRadioGroupContext(). | |
RadioItemProps | interface | props | 0 | Individual radio option inside a RadioGroup.
Renders a hidden native <input type="radio"> for form semantics and a styled visual indicator.
The indicator exposes a data-state attribute (checked | unchecked) for CSS-based custom styling. | |
MintProp | type | helper | 1 | — | |
InteractiveTier | type | helper | 1 | Semantic radius tier for interactive surfaces (3-tier system).
- commit → r-human (CTA, identity, status declarations)
- modify → r-interactive (fields, navigation, secondary actions)
Container components (Card, Alert, Toolbar surface, …) live in a third
tier contain (r-structure) which is **not** part of this propagation
context — those surfaces are always r-structure by design and have no
tier-flip use case. | |
RadioGroupSlots | type | variant | 0 | Slot names derived from the tv() config above — single source of truth for slotClasses. | |
RadioGroupVariants | type | variant | 0 | — | |
RadioItemSlots | type | variant | 0 | — | |
RadioItemVariants | type | variant | 0 | — | |
MintName | type | helper | 0 | A mint name: a built-in (autocompleted), 'none' to disable, or any
consumer-registered name. (string & {}) keeps the registry open — a
custom name still type-checks, it just isn't suggested. A typo therefore
also still compiles (it resolves like an unregistered custom name and
warns at runtime); the union buys completion and docs, not validation. | |
MintConfig | interface | helper | 0 | — | |
SlotNames | type | helper | 0 | Extracts the slot-name union from a slotted tv() config function — the
companion to VariantProps. The slot-mode overload returns
(props?) => { [K in keyof S]: SlotFn }, so keyof ReturnType<T> is exactly
the set of slot names a component declares in tv({ slots: … }).
Use it to type a component's slotClasses prop from the single source of
truth (its *.variants.ts) instead of hand-maintaining a parallel union
that silently drifts when a slot is added or renamed: | |
VariantProps | type | helper | 0 | — | |
BuiltinMintName | type | helper | 0 | Built-in mint names as a literal union, so the mint prop autocompletes
across every component — the single list the hand-curated playground knobs
and docs used to drift away from. |
06 Installation
Import
import { RadioGroup, RadioItem } from '@urbicon-ui/blocks';