Radio Group
Overview
The Radio Group is a layout component that arranges related radios into a single, consistently spaced set. The useRadioGroupRecipe() composable creates a fully configured recipe with orientation and size options — a flex container that stacks radios vertically or lays them out in a wrapping row.
The group handles layout only. Single selection comes from the native input: give every child radio the same name, and the browser enforces one choice per group and wires up arrow-key navigation. Mark the container with role="radiogroup" (or use a <fieldset>) so assistive technology announces the set.
The Radio Group recipe integrates directly with the Radio recipe and generates type-safe utility classes at build time with zero runtime CSS.
Why use the Radio Group recipe?
The Radio Group recipe helps you:
- Lay out sets consistently: Stack radios vertically or wrap them in a row with a single orientation prop.
- Control spacing with one axis: The size variant sets the gap between items, so a whole group scales together.
- Compose, don't couple: The group only handles layout; each child radio keeps its own color, size, and state.
- Stay type-safe: Full TypeScript support means your editor catches invalid orientation or size values at compile time.
Usage
Register the recipe
Add the Radio Group recipe to a local Styleframe instance alongside the radio recipes. The global styleframe.config.ts provides design tokens and utilities, while the component-level file registers the recipes themselves:
import { styleframe } from 'virtual:styleframe';
import {
useRadioRecipe,
useRadioFieldRecipe,
useRadioGroupRecipe,
} from '@styleframe/theme';
const s = styleframe();
const radio = useRadioRecipe(s);
const radioField = useRadioFieldRecipe(s);
const radioGroup = useRadioGroupRecipe(s);
export default s;
Build the component
Import the radioGroup runtime function from the virtual module and wrap your radios in the container. Mark it with role="radiogroup" and give every child radio the same name:
import { radioGroup } from "virtual:styleframe";
interface RadioGroupProps {
orientation?: "vertical" | "horizontal";
size?: "sm" | "md" | "lg";
children?: React.ReactNode;
}
export function RadioGroup({
orientation = "vertical",
size = "md",
children,
}: RadioGroupProps) {
const classes = radioGroup({ orientation, size });
return (
<div className={classes} role="radiogroup">
{children}
</div>
);
}
See it in action
Orientation
The Radio Group supports two orientation variants that control the layout direction.
Vertical
The default orientation. Radios are stacked top-to-bottom in a column — the most common layout for option lists.
Horizontal
Radios are laid out left-to-right in a wrapping row, aligned to the center. Useful for short option sets that fit inline.
Orientation Reference
| Orientation | Direction | Notes |
|---|---|---|
vertical | Top to bottom (column) | Default; one option per line |
horizontal | Left to right (row, wraps) | Center-aligned, wraps to the next line when out of space |
Sizes
Three size variants control the gap between radios, letting you tighten or loosen a group to match its surroundings. Set the same size on each child radio so the controls and the spacing scale together.
| Size | Gap |
|---|---|
sm | @0.5 |
md | @0.75 |
lg | @1 |
size controls only the gap between items. Pass the same size to each child <Radio> so the circles and the spacing scale as a set.Accessibility
- Mark the group. Wrap the radios in an element with
role="radiogroup"and anaria-label(oraria-labelledby) describing the set, so assistive technology announces the context. - Share a
nameacross children. Single selection and arrow-key navigation come from giving every child radio the samename. The group recipe only handles layout. - Prefer a fieldset for forms. When the group is part of a form, a native
<fieldset>with a<legend>is the most robust grouping — apply the recipe class to the fieldset. - Keep each radio labelled. The group provides layout only; every child still needs its own label (see the Radio recipe).
Customization
Overriding Defaults
The useRadioGroupRecipe() composable accepts an optional second argument to override any part of the recipe configuration. Overrides are deep-merged with the defaults, so you only need to specify the properties you want to change:
import { styleframe } from 'virtual:styleframe';
import { useRadioGroupRecipe } from '@styleframe/theme';
const s = styleframe();
const radioGroup = useRadioGroupRecipe(s, {
defaultVariants: {
orientation: 'horizontal',
size: 'lg',
},
});
export default s;
Filtering Variants
If you only need a subset of the available variants, use the filter option to limit which values are generated. This reduces the output CSS and keeps your component API focused:
import { styleframe } from 'virtual:styleframe';
import { useRadioGroupRecipe } from '@styleframe/theme';
const s = styleframe();
// Only generate the vertical orientation
const radioGroup = useRadioGroupRecipe(s, {
filter: {
orientation: ['vertical'],
},
});
export default s;
API Reference
useRadioGroupRecipe(s, options?)
Creates a radio group recipe — a flex container with orientation and size (gap) variants.
Parameters:
| Parameter | Type | Description |
|---|---|---|
s | Styleframe | The Styleframe instance |
options | DeepPartial<RecipeConfig> | Optional overrides for the recipe configuration |
options.base | VariantDeclarationsBlock | Custom base styles for the group |
options.variants | Variants | Custom variant definitions for the recipe |
options.defaultVariants | Record<keyof Variants, string> | Default variant values for the recipe |
options.filter | Record<string, string[]> | Limit which variant values are generated |
Variants:
| Variant | Options | Default |
|---|---|---|
orientation | vertical, horizontal | vertical |
size | sm, md, lg | md |
Best Practices
- Pair with the Radio recipe: The group handles layout; each child should use the Radio recipe for consistent controls.
- Share a
name: Give every child radio the samenameso the set behaves as one mutually-exclusive group. - Match sizes: Use the same
sizeon the group and its child radios so spacing and circles scale together. - Keep groups scannable: Prefer vertical layout for longer option lists; reserve horizontal for two or three short options.
- Label the set: Always describe the group's purpose with
aria-labelor a<legend>so screen readers communicate the relationship between options.
FAQ
name and the browser enforces one choice per group. Pass the same size to the group and its children if you want the spacing and the circles to scale together.horizontal for short sets of two or three options that read well inline. It lays the radios out in a centered, wrapping row. For longer option lists, the default vertical orientation is easier to scan.radioGroup() class to a native <fieldset> and add a <legend> for the group name. The recipe only sets layout properties, so it works on any block-level element.filter option, variant values you exclude are not generated, and default variants are adjusted if they reference a removed value — so the recipe stays consistent and only emits the CSS you use.Radio
A custom radio built on the native input, with a CSS dot indicator, checked, disabled, and focus states, light, dark, and neutral surface colors, and three sizes through the recipe system.
Select
A multi-select form control composed of a trigger, a floating listbox panel, selectable options, dismissable value chips, a chevron indicator, group labels, and separators. Supports light, dark, and neutral colors, per-part visual styles, and three sizes through the recipe system.