Browse docs
Browse docs
A single-selection list. Backed by Radix RadioGroup primitive — keyboard navigation (arrows + Space), role="radiogroup", and focus management come for free. Bridge-integrated so it slots into <DashForm> like every other Dashforge input, and uniquely supports per-option RBAC (each option can hide/disable/readonly independently).
import { RadioGroup } from '@dashforge/tw';
<RadioGroup
name="plan"
label="Plan"
options={[
{ value: 'free', label: 'Free' },
{ value: 'pro', label: 'Pro' },
{ value: 'team', label: 'Team' },
]}
defaultValue="free"
/>Inside <DashForm>:
import { DashForm } from '@dashforge/forms';
import { RadioGroup, Button } from '@dashforge/tw';
<DashForm onSubmit={onSubmit}>
<RadioGroup
name="contactMethod"
label="How should we reach you?"
options={[
{ value: 'email', label: 'Email' },
{ value: 'sms', label: 'SMS' },
{ value: 'phone', label: 'Phone call' },
]}
required
/>
<Button type="submit" color="primary">Continue</Button>
</DashForm>Standalone (no form):
const [value, setValue] = useState('email');
<RadioGroup
name="contactMethod"
options={OPTIONS}
value={value}
onValueChange={setValue}
/>import { RadioGroup } from '@dashforge/tw';
<RadioGroup
name="plan"
label="Choose a plan"
defaultValue="pro"
options={[
{ value: 'free', label: 'Free — 1 workspace, community support' },
{ value: 'pro', label: 'Pro — unlimited workspaces, email support' },
{ value: 'team', label: 'Team — SSO, audit log, priority support' },
]}
/>import { RadioGroup } from '@dashforge/tw';
<RadioGroup
name="size"
label="Size"
layout="row"
defaultValue="m"
options={[
{ value: 's', label: 'S' },
{ value: 'm', label: 'M' },
{ value: 'l', label: 'L' },
{ value: 'xl', label: 'XL' },
]}
/><RadioGroup name="size" layout="stacked" options={SIZES} />
<RadioGroup name="size" layout="row" options={SIZES} />stacked is the default — best for 3+ options with descriptive labels. row works for binary-ish choices (yes/no/maybe) where horizontal density beats per-row readability.
<RadioGroup size="sm" options={OPTIONS} />
<RadioGroup size="md" options={OPTIONS} />
<RadioGroup size="lg" options={OPTIONS} />The killer feature. Each option carries its own access:
<RadioGroup
name="plan"
options={[
{ value: 'free', label: 'Free' },
{ value: 'pro', label: 'Pro' },
{
value: 'team',
label: 'Team (admin only)',
access: { requires: 'workspace.admin', when: 'denied:hide' },
},
]}
/>Non-admin users see only Free + Pro. Group-level access (if also passed) takes precedence — when the whole group is denied, no per-option override resurrects it.
<RadioGroup
name="terms"
label="Do you accept the terms?"
options={[{ value: 'yes', label: 'Yes' }, { value: 'no', label: 'No' }]}
required
rules={{ validate: (v) => v === 'yes' || 'You must accept to continue' }}
/>required adds the asterisk marker + RHF required rule. Custom rules (RHF format) layer on top.
<RadioGroup name="hasAccount" options={[{ value: 'yes', label: 'Yes' }, { value: 'no', label: 'No' }]} />
<TextField
name="email"
label="Email"
visibleWhen={(engine) => engine.getNode('hasAccount')?.value === 'yes'}
/>Configure <RadioGroup> defaults application-wide.
import { patchTheme } from '@dashforge/tw-theme';
patchTheme({
components: {
RadioGroup: {
defaults: { size: 'md', layout: 'stacked' },
},
},
});Configurable axes (RadioGroupVariantProps):
| Axis | Type | Notes |
|---|---|---|
size | 'sm' | 'md' | 'lg' | Control circle + label font-size. |
layout | 'stacked' | 'row' | Option list direction (vertical vs horizontal). |
Non-visual axes (name, rules, options, label, helperText, error, disabled, access, visibleWhen, onValueChange) are not theme-configurable — per-instance semantics.
Precedence chain (lowest → highest):
defaultVariants from the internal radioGroupVariants recipe.theme.components.RadioGroup.defaults (application-wide).<RadioGroup size="lg" layout="row" />) — wins over theme.sx — appended to the root and wins over conflicting classes via tailwind-merge.TypeScript: theme.components.RadioGroup.defaults autocompletes to the two axes above.
Reactivity: useComponentDefaults('RadioGroup') subscribes to the theme store — patchTheme re-renders every mounted instance inheriting the changed axis.
| Prop | Type | Default | Description |
|---|---|---|---|
name | string | — | Field name registered with the bridge. |
options | RadioGroupOption[] | — | Array of selectable options. Order is render order. |
access | AccessRequirement | — | Group-level RBAC requirement (precedes option-level access). |
defaultValue | string | — | Default value for uncontrolled mode (no-op in form mode — bridge wins). |
disabled | boolean | — | Explicit disable override (ORed with RBAC). |
error | boolean | — | Explicit error override (otherwise resolved from bridge). |
helperText | ReactNode | — | Helper text shown below the option list when not in error state. |
label | ReactNode | — | Group-level label (rendered above the option list). |
layout | 'stacked' | 'row' | 'stacked' | Option list direction. 'stacked' (default) is vertical; 'row' is horizontal. |
onValueChange | (value: string) => void | — | Change handler (called after bridge update in form mode). |
required | boolean | — | Marks the group as required (renders red * after the label). |
rules | unknown | — | React Hook Form validation rules — forwarded to bridge.register. |
size | 'md' | 'sm' | 'lg' | 'md' | Density tier — drives radio dot size + label font-size. |
slotProps | RadioGroupSlotProps | — | Per-slot className overrides. |
sx | string | — | Root className shortcut (cn'd with the variant root class). |
value | string | — | Controlled value (for standalone mode without a form bridge). |
visibleWhen | (engine: Engine) => boolean | — | Engine predicate — group not rendered when it returns false. |
<RadioGroup> is a compound component with 10 named slots. Each slot accepts a { className?: string } override via slotProps:
| Slot | Purpose |
|---|---|
root | Outer wrapper (label + option list + helper/error). |
label | The group-level <label> element. |
requiredMark | The red * next to the label when required. |
optionList | The container holding all radio options (direction driven by layout). |
option | Wrapper for a single option (control + label). |
control | The visible radio circle — Radix RadioGroup.Item. |
indicator | The inner dot rendered when selected. |
optionLabel | The text label of a single option. |
helperText | Group-level helper text, when not in error state. |
errorText | Group-level helper text, when in error state. |
Use sx for a root-level class override; use slotProps when you need to target a specific per-option element (e.g. control border when unselected, indicator fill color).
@dashforge/tw exposes it). Use it for plan/role pickers where some choices are admin-only.aria-checked are not hand-rolled.string (the option's value field). Cast at form-submit time if you need numbers.<Switch> (binary toggle) or two separate <Checkbox> — RadioGroup is best from 3 options up.