RadioCardGroup
Forms
Smart auto-detect
Introduction
A radio group presented as a set of selectable cards, for exclusive choices that need more visual weight than plain radio buttons — pricing tiers, shipping speeds, and similar.
Hydration
Tier 2 — smart auto-detect. A RadioCardGroup renders as static HTML
and ships no client JS unless a behavioral signal is present. Pass
interactive={true} to force hydration, or interactive={false} to force
a static render.
It hydrates as an island when any of the following signals is present
(or interactive={true} is set):
value(controlled selection)defaultValue(uncontrolled initial selection)onValueChange
interactive prop | Result |
|---|---|
| omitted, no signal | Static — no client JS |
| omitted, a signal present | Hydrates as an island |
true | Hydrates as an island |
false | Static — no client JS |
Usage
Plan
Billing
import { RadioCardGroup } from "../components/ui";
export default function MyPage() {
return (
<RadioCardGroup
label="Plan"
defaultValue="pro"
items={[
{ label: "Hobby", value: "hobby" },
{ label: "Pro", value: "pro" },
{ label: "Enterprise", value: "enterprise" },
]}
/>
);
}
Custom Composition
Pass children for full control over each card's contents instead of the
items array:
import { RadioCardGroup } from "../components/ui";
export default function MyPage() {
return (
<RadioCardGroup label="Plan" defaultValue="pro">
<RadioCardGroup.Item value="hobby">
<RadioCardGroup.ItemText>Hobby — $0/mo</RadioCardGroup.ItemText>
<RadioCardGroup.ItemControl />
<RadioCardGroup.ItemHiddenInput />
</RadioCardGroup.Item>
<RadioCardGroup.Item value="pro">
<RadioCardGroup.ItemText>Pro — $20/mo</RadioCardGroup.ItemText>
<RadioCardGroup.ItemControl />
<RadioCardGroup.ItemHiddenInput />
</RadioCardGroup.Item>
<RadioCardGroup.Indicator />
</RadioCardGroup>
);
}
CMS Page Builder
This component is available as a radioCardGroup block in the
Page Builder (content/pages/*.json):
{
"type": "radioCardGroup",
"label": "Plan",
"defaultValue": "pro",
"variant": "outline",
"items": [
{ "label": "Hobby", "value": "hobby" },
{ "label": "Pro", "value": "pro" },
{ "label": "Enterprise", "value": "enterprise", "disabled": true }
]
}
It always renders interactive in the Page Builder.
Props
Root
| Prop | Type | Description |
|---|---|---|
value | string | The currently selected value (controlled). |
defaultValue | string | The initially selected value (uncontrolled). |
onValueChange | (details: { value: string }) => void | Called when the selection changes. |
id | string | The root element's id. |
name | string | Name of the underlying radio inputs, for native form submission. |
disabled | boolean | Disables every item. |
readOnly | boolean | Prevents selection changes while still allowing focus. |
variant | "subtle" | "outline" | "surface" | "solid" | Visual style. Default "outline". |
size | "md" | Visual size. |
colorPalette | "blue" | "green" | "red" | "purple" | "orange" | "amber" | "cyan" | "slate" | "gray" | The color theme. |
interactive | boolean | Overrides the hydration decision (see above). |
class | string | Custom CSS classes for the root element. |
Default composition
| Prop | Type | Description |
|---|---|---|
items | (string | RadioCardGroupItem)[] | Cards to render. A plain string is shorthand for { value, label: value }. |
label | string | JSX.Element | Label rendered above the cards. |
RadioCardGroupItem
| Prop | Type | Description |
|---|---|---|
value | string | The item's unique value. |
label | string | JSX.Element | The card's display content. |
disabled | boolean | Disables this card. |
invalid | boolean | Marks this card invalid. |
Sub-components
| Part | Description |
|---|---|
RadioCardGroup.Label | The group's label (<span>). |
RadioCardGroup.Item | One selectable card. role="radio", requires a value prop. |
RadioCardGroup.ItemText | The card's display text/content. |
RadioCardGroup.ItemControl | The card's visual radio indicator wrapper. |
RadioCardGroup.ItemHiddenInput | The visually-hidden native <input type="radio"> that holds the selection for form submission. |
RadioCardGroup.Indicator | A shared sliding indicator element for the checked state. |
Accessibility
- The root has
role="radiogroup"; each item hasrole="radio"witharia-checkedreflecting selection. - Items use roving
tabIndex— only the checked (or first enabled) item is in the tab sequence, matching native radio-group keyboard behavior. data-state(checked/unchecked) anddata-disabledare mirrored onto items and their text/control parts for styling.