RadioGroup

Use in a form to allow selecting one option from multiple

RadioGroup lets users choose one option from a keyboard-navigable set on web and native.

Features

  • Accessible, composable, and customizable

  • Sizing with controlled or uncontrolled state

  • Optional native HTML radio buttons on web

Installation

RadioGroup is already installed in tamagui, or you can install it independently:

yarn add @tamagui/radio-group

Usage

// copy-paste: these skins import from files you own. copy each registry item
// below into your app, then adjust the relative import paths to fit.
// components/tamagui/RadioGroup.tsx (registry item "radiogroup")
// npm dependencies: @tamagui/core @tamagui/radio-group
import { RadioGroup } from "../components/tamagui/RadioGroup";
export default () => <RadioGroup value="foo" gap="2">
<RadioGroup.Item value="foo" id="foo-radio-item">
<RadioGroup.Indicator />
</RadioGroup.Item>
<RadioGroup.Item value="bar" id="bar-radio-item">
<RadioGroup.Indicator />
</RadioGroup.Item>
</RadioGroup>;

Headless usage

To use headless without @tamagui/core, install @tamagui/radio-headless:

yarn add @tamagui/radio-headless

The @tamagui/radio-headless package provides three hooks for building custom radio groups:

  • useRadioGroup - for the container/group
  • useRadioGroupItem - for individual radio items
  • useRadioGroupItemIndicator - for the indicator (checked state visual)
import {
useRadioGroup,
useRadioGroupItem,
useRadioGroupItemIndicator,
RadioGroupContextValue,
RadioGroupItemContextValue,
} from '@tamagui/radio-headless'
import { createContext, useContext } from 'react'
// Create contexts for the group and item
const RadioGroupContext = createContext<RadioGroupContextValue>({})
const RadioGroupItemContext = createContext<RadioGroupItemContextValue>({
checked: false,
})
function RadioGroup({ children, ...props }) {
const { providerValue, frameAttrs } = useRadioGroup({
orientation: 'vertical',
...props,
})
return (
<RadioGroupContext.Provider value={providerValue}>
<div {...frameAttrs} style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
{children}
</div>
</RadioGroupContext.Provider>
)
}
function RadioItem({ value, children }) {
const { checked, providerValue, frameAttrs, bubbleInput } = useRadioGroupItem({
radioGroupContext: RadioGroupContext,
value,
})
return (
<RadioGroupItemContext.Provider value={providerValue}>
<button {...frameAttrs} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: 8, border: '1px solid #ccc', borderRadius: 4, background: checked ? '#e0f2fe' : 'white', }} >
<RadioIndicator />
{children}
</button>
{bubbleInput}
</RadioGroupItemContext.Provider>
)
}
function RadioIndicator() {
const { checked, ...indicatorProps } = useRadioGroupItemIndicator({
radioGroupItemContext: RadioGroupItemContext,
})
return (
<div {...indicatorProps} style={{ width: 16, height: 16, borderRadius: '50%', border: '2px solid #3b82f6', display: 'flex', alignItems: 'center', justifyContent: 'center', }} >
{checked && (
<div style={{ width: 8, height: 8, borderRadius: '50%', background: '#3b82f6' }} />
)}
</div>
)
}
// Usage
function App() {
return (
<RadioGroup defaultValue="option1" onValueChange={console.log}>
<RadioItem value="option1">Option 1</RadioItem>
<RadioItem value="option2">Option 2</RadioItem>
<RadioItem value="option3">Option 3</RadioItem>
</RadioGroup>
)
}

API reference

RadioGroup

RadioGroup extends View, inheriting all the Tamagui standard props, plus:

Props

  • name

    string

    Equivalent to input name attribute.

  • value

    string

    Controlled value of the selected radio item.

  • defaultValue

    string

    Default value for uncontrolled usage.

  • required

    boolean

    Sets aria-required attribute.

  • disabled

    boolean

    Sets aria-disabled on web, and disables touch on native for all children items.

  • native

    boolean

    Default: 

    false

    Renders native radio button on web.

  • onValueChange

    (value: string) => void

    Called when the selected value changes.

  • orientation

    "horizontal" | "vertical"

    Orientation of the radio group.

  • accentColor

    string

    Sets `accent-color` style when `native` prop is enabled.

  • RadioGroup.Item

    Item sizes come from the shared Sizing ladder.

    Props

  • size

    'xs' | 'sm' | 'md' | 'lg' | 'xl'

    A named size ("xs" to "xl", default "md") is a rung in the sizing ladder: the circle is the ladder's square derivation.

  • labeledBy

    string

    Sets aria-labelledby on web.

  • value

    string

    Input value for the radio button.

  • disabled

    boolean

    Sets aria-disabled on web, and disables touch on native.

  • id

    string

    ID used on the web.

  • unstyled

    boolean

    Default: 

    false

    When true, removes all default Tamagui styling.

  • RadioGroup.Indicator

    RadioGroup.Indicator appears only when the parent Item is checked. It extends YStack, getting Tamagui standard props adding:

    Props

  • unstyled

    boolean

    Removes all default Tamagui styles.

  • Headless props

    useRadioGroup return value

    PropertyTypeDescription
    providerValueRadioGroupContextValueValue to pass to your context provider
    frameAttrsobjectProps to spread on the group container (role, aria-orientation, etc.)
    rovingFocusGroupAttrsobjectProps for roving focus behavior (optional)
    Property
    providerValue
    Type
    RadioGroupContextValue
    Description
    Value to pass to your context provider
    Property
    frameAttrs
    Type
    object
    Description
    Props to spread on the group container (role, aria-orientation, etc.)
    Property
    rovingFocusGroupAttrs
    Type
    object
    Description
    Props for roving focus behavior (optional)

    useRadioGroupItem

    Hook for individual radio items. Requires a context containing the group state.

    const {
    checked,
    isFormControl,
    providerValue,
    bubbleInput,
    native,
    frameAttrs,
    rovingFocusGroupAttrs,
    } = useRadioGroupItem({
    radioGroupContext: YourRadioGroupContext,
    value: 'option1',
    id: 'option1-id',
    labelledBy: 'label-id',
    disabled: false,
    onPress: () => {},
    onKeyDown: () => {},
    onFocus: () => {},
    })
    PropertyTypeDescription
    checkedbooleanWhether this item is currently selected
    providerValueobjectContext value for the indicator
    frameAttrsobjectProps to spread on the item element
    bubbleInputReactNodeHidden input for form compatibility
    nativebooleanWhether using native radio
    isFormControlbooleanWhether inside a form
    Property
    checked
    Type
    boolean
    Description
    Whether this item is currently selected
    Property
    providerValue
    Type
    object
    Description
    Context value for the indicator
    Property
    frameAttrs
    Type
    object
    Description
    Props to spread on the item element
    Property
    bubbleInput
    Type
    ReactNode
    Description
    Hidden input for form compatibility
    Property
    native
    Type
    boolean
    Description
    Whether using native radio
    Property
    isFormControl
    Type
    boolean
    Description
    Whether inside a form

    useRadioGroupItemIndicator

    Hook for the visual indicator of checked state.

    const { checked, ...dataAttrs } = useRadioGroupItemIndicator({
    radioGroupItemContext: YourRadioGroupItemContext,
    disabled: false,
    })
    PropertyTypeDescription
    checkedbooleanWhether the parent item is checked
    data-statestring‘checked’ or ‘unchecked’
    data-disabledstring | undefinedPresent when disabled
    Property
    checked
    Type
    boolean
    Description
    Whether the parent item is checked
    Property
    data-state
    Type
    string
    Description
    ‘checked’ or ‘unchecked’
    Property
    data-disabled
    Type
    string | undefined
    Description
    Present when disabled

    Source

    v2-look RadioGroup: token-based item sizing, theme background/border/focus styling, disabled dimming, and a centered color indicator over the unstyled @tamagui/ui RadioGroup behavior. This is the exact source the registry ships — copy it into your app and the examples above import from your copy.

    components/tamagui/RadioGroup.tsx
    import { type ComponentSize, type GetProps, resolveSizing, styled, withStaticProperties, } from '@tamagui/core' import { RadioGroup as RadioGroupBehavior } from '@tamagui/radio-group' export type RadioGroupSize = ComponentSize | boolean // a radio reads as a control next to its label, so it matches the checkbox // square and the switch track height rather than the icon square const getRadioSize = styled.dynamic<RadioGroupSize>((val, env) => { const sizing = resolveSizing(val, env) if (!sizing) return return { width: sizing.square, height: sizing.square, } }) export const RadioGroupFrame = styled(RadioGroupBehavior, { displayName: 'RadioGroup', }) export const RadioGroupItem = styled(RadioGroupBehavior.Item, { displayName: 'RadioGroupItem', // selected swaps the whole item onto the brand theme, the same convention // Checkbox, Switch and ToggleGroup.Item use, so the four read as one family activeTheme: 'brand', alignItems: 'center', justifyContent: 'center', backgroundColor: 'background hover:background-hover press:background-press', borderColor: 'border-color hover:border-color-hover press:border-color-press', borderRadius: 1000, borderWidth: 1, outlineColor: 'focus-visible:outline-color', outlineStyle: 'focus-visible:solid', outlineWidth: 'focus-visible:2px', variants: { size: getRadioSize, disabled: { true: { cursor: 'not-allowed', opacity: 0.45, }, }, } as const, defaultVariants: { size: 'md', }, }) export const RadioGroupIndicator = styled(RadioGroupBehavior.Indicator, { displayName: 'RadioGroupIndicator', width: '50%', height: '50%', borderRadius: 1000, backgroundColor: 'color', }) export const RadioGroup = withStaticProperties(RadioGroupFrame, { Item: RadioGroupItem, Indicator: RadioGroupIndicator, }) export type RadioGroupProps = GetProps<typeof RadioGroup>

    Dependencies

    yarn add @tamagui/core @tamagui/radio-group

    Expects theme tokens: background, background-hover, background-press, border-color, border-color-hover, border-color-press, outline-color, color.

    Need raw behavior without any skin? tamagui/unstyled re-exports the @tamagui/ui primitives (advanced).