All components
Radio Group
Single-choice option lists with outline, subtle, and solid radio controls.
123456789101112131415161718import { RadioGroup } from "#components/ui/ark-ui/radio-group.tsx";import { indigoPalette } from "@stylexblocks/ui/themes/palettes.ts";
export function Example() { return ( // variant/size resolve once on Root; ItemControl renders the dot from the // machine's checked state <RadioGroup.Root defaultValue="growth" variant="solid" size="md" palette={indigoPalette} aria-label="Plan"> <RadioGroup.Item value="growth"> <RadioGroup.ItemControl /> <RadioGroup.ItemText>Growth</RadioGroup.ItemText> <RadioGroup.ItemHiddenInput /> </RadioGroup.Item> </RadioGroup.Root> );}
// controlled: <RadioGroup.Root value={plan} onValueChange={(d) => setPlan(d.value)}>Variants
outline
subtle
solid
Sizes
xs
sm
md
lg
Install
1npx stylexblocks add radio-groupSource
components/ui/ark-ui/radio-group.tsx
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254import * as stylex from "@stylexjs/stylex";import { RadioGroup as ArkRadioGroup, useRadioGroupItemContext } from "@ark-ui/react/radio-group";import { createSlotRecipeContext } from "#lib/ark-ui/create-slot-recipe-context.tsx";import { defineSlotRecipe, resolveSlotStyles } from "#lib/recipe-props.ts";import { radioGroupItemControlDotVars } from '@stylexblocks/ui/slot-recipes/radio-group.stylex.ts'
import { colorPalette } from '@stylexblocks/ui/color-palette.stylex.ts'import { colors } from '@stylexblocks/ui/tokens/colors.stylex.ts'import { cursor } from '@stylexblocks/ui/tokens/cursor.stylex.ts'import { fontSizes } from '@stylexblocks/ui/tokens/font-sizes.stylex.ts'import { fontWeights } from '@stylexblocks/ui/tokens/font-weights.stylex.ts'import { lineHeights } from '@stylexblocks/ui/tokens/line-heights.stylex.ts'import { radii } from '@stylexblocks/ui/tokens/radii.stylex.ts'import { semanticColors } from '@stylexblocks/ui/semantic-tokens/colors.stylex.ts'import { sizes } from '@stylexblocks/ui/tokens/sizes.stylex.ts'import { spacing } from '@stylexblocks/ui/tokens/spacing.stylex.ts'
const radioGroupItem = stylex.create({ base: { display: 'inline-flex', alignItems: 'center', position: 'relative', fontWeight: fontWeights.medium, cursor: { default: null, ':is(:disabled, [disabled], [data-disabled])': cursor.disabled, }, },})
const radioGroupItemSizes = stylex.create({ xs: { fontSize: fontSizes.xs, lineHeight: lineHeights.xs, gap: spacing._1_5, }, sm: { fontSize: fontSizes.sm, lineHeight: lineHeights.sm, gap: spacing._2, }, md: { fontSize: fontSizes.sm, lineHeight: lineHeights.sm, gap: spacing._2_5, }, lg: { fontSize: fontSizes.md, lineHeight: lineHeights.md, gap: spacing._3, },})
const radioGroupItemControl = stylex.create({ base: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, verticalAlign: 'top', color: colors.white, borderWidth: '1px', borderColor: { default: 'transparent', ':is([data-invalid], [aria-invalid="true"])': semanticColors.borderDestructive, }, borderRadius: radii.full, cursor: { default: cursor.radio, ':is(:disabled, [disabled], [data-disabled])': cursor.disabled, }, outlineWidth: { default: null, ':focus-visible': '1px', }, outlineOffset: { default: null, ':focus-visible': '2px', }, outlineStyle: { default: null, ':focus-visible': 'solid', }, outlineColor: { default: null, ':focus-visible': colorPalette.focusRing, }, opacity: { default: null, ':is(:disabled, [disabled], [data-disabled])': 0.5, }, [radioGroupItemControlDotVars.height]: '100%', [radioGroupItemControlDotVars.width]: '100%', [radioGroupItemControlDotVars.borderRadius]: radii.full, [radioGroupItemControlDotVars.backgroundColor]: 'currentColor', [radioGroupItemControlDotVars.scale]: 0.4, },})
const radioGroupItemControlSizes = stylex.create({ xs: { width: sizes._3, height: sizes._3, }, sm: { width: sizes._3_5, height: sizes._3_5, }, md: { width: sizes._4, height: sizes._4, }, lg: { width: sizes._5, height: sizes._5, },})
const radioGroupItemControlVariants = stylex.create({ outline: { borderWidth: '1px', borderColor: { default: 'inherit', ':is([data-invalid], [aria-invalid="true"])': semanticColors.borderDestructive, ':is(:checked, [data-checked], [aria-checked="true"], [data-state="checked"])': colorPalette.solid, }, color: { default: colors.white, ':is(:checked, [data-checked], [aria-checked="true"], [data-state="checked"])': colorPalette.fg, }, [radioGroupItemControlDotVars.scale]: 0.6, }, subtle: { borderWidth: '1px', backgroundColor: colorPalette.muted, borderColor: { default: colorPalette.muted, ':is([data-invalid], [aria-invalid="true"])': semanticColors.borderDestructive, }, color: { default: 'transparent', ':is(:checked, [data-checked], [aria-checked="true"], [data-state="checked"])': colorPalette.fg, }, }, solid: { borderWidth: '1px', borderColor: { default: semanticColors.border, ':is([data-invalid], [aria-invalid="true"])': semanticColors.borderDestructive, ':is(:checked, [data-checked], [aria-checked="true"], [data-state="checked"])': colorPalette.solid, }, backgroundColor: { default: null, ':is(:checked, [data-checked], [aria-checked="true"], [data-state="checked"])': colorPalette.solid, }, color: { default: colors.white, ':is(:checked, [data-checked], [aria-checked="true"], [data-state="checked"])': colorPalette.contrast, }, },})
const radioGroupItemControlDot = stylex.create({ base: { height: radioGroupItemControlDotVars.height, width: radioGroupItemControlDotVars.width, borderRadius: radioGroupItemControlDotVars.borderRadius, backgroundColor: radioGroupItemControlDotVars.backgroundColor, scale: radioGroupItemControlDotVars.scale, },})
const radioGroupLabel = stylex.create({ base: { userSelect: 'none', fontSize: fontSizes.sm, lineHeight: lineHeights.sm, opacity: { default: null, ':is(:disabled, [disabled], [data-disabled])': 0.5, }, },})
export type RadioGroupVariant = keyof typeof radioGroupItemControlVariants
export type RadioGroupSize = keyof typeof radioGroupItemSizes
const radioGroupRecipe = defineSlotRecipe({ slots: { item: { styles: radioGroupItem, sizes: radioGroupItemSizes }, itemControl: { styles: radioGroupItemControl, sizes: radioGroupItemControlSizes, variants: radioGroupItemControlVariants, }, label: { styles: radioGroupLabel }, }, defaultVariants: { size: "md", variant: "solid" },});
const chrome = stylex.create({ root: { display: "flex", flexDirection: "column", gap: spacing._2_5 }, cursorPointer: { cursor: "pointer" },});
function ItemControlNode({ children, ...props}: React.ComponentProps<typeof ArkRadioGroup.ItemControl>) { const item = useRadioGroupItemContext(); return ( <ArkRadioGroup.ItemControl {...props} data-checked={item.checked ? "" : undefined}> {children ?? (item.checked ? <span {...stylex.props(radioGroupItemControlDot.base)} /> : null)} </ArkRadioGroup.ItemControl> );}
const { withProvider, withContext } = createSlotRecipeContext(radioGroupRecipe);
/** * Radio Group — Ark UI's radio-group machine wearing the radio-group slot * recipe. `<RadioGroup.Root variant size>` resolves the recipe once and every * item inherits it through context. ItemControl copies `data-checked` from * the item machine so the recipe's checked selectors apply. */export const RadioGroup = { Root: withProvider(ArkRadioGroup.Root, () => [chrome.root], "RadioGroupRoot"), Item: withContext( ArkRadioGroup.Item, (v) => [...resolveSlotStyles(radioGroupRecipe.slots.item, v), chrome.cursorPointer], "RadioGroupItem", ), ItemControl: withContext(ItemControlNode, "itemControl", "RadioGroupItemControl"), ItemText: withContext(ArkRadioGroup.ItemText, "label", "RadioGroupItemText"), ItemHiddenInput: ArkRadioGroup.ItemHiddenInput,};
export type RadioGroupRootProps = React.ComponentProps<typeof RadioGroup.Root>;export type RadioGroupItemProps = React.ComponentProps<typeof RadioGroup.Item>;export type RadioGroupItemControlProps = React.ComponentProps<typeof RadioGroup.ItemControl>;export type RadioGroupItemTextProps = React.ComponentProps<typeof RadioGroup.ItemText>;