code/tamagui.dev/data/docs/components/radio-group/2.0.0.mdx
<Tabs.Content value="styled">
</Tabs.Content> <Tabs.Content value="unstyled">
</Tabs.Content> <Tabs.Content value="headless">
</Tabs.Content>
<Highlights
features={[
Accessible, easy to compose and customize.,
Sizable & works controlled or uncontrolled.,
Ability to opt-out to native radio button on web.,
]}
/>
<Tabs.Content value="styled">
RadioGroup is already installed in tamagui, or you can install it independently:
npm install @tamagui/radio-group
</Tabs.Content>
<Tabs.Content value="unstyled">
RadioGroup is already installed in tamagui, or you can install it independently:
npm install @tamagui/radio-group
</Tabs.Content>
<Tabs.Content value="headless">
To use the headless radio group, import from the @tamagui/radio-headless package. This package has no dependency on @tamagui/core and provides hooks for building custom radio groups with any styling solution.
npm install @tamagui/radio-headless
</Tabs.Content>
<Tabs.Content value="styled">
import { RadioGroup } from 'tamagui'
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>
)
</Tabs.Content>
<Tabs.Content value="unstyled">
Use the createRadioGroup export to create a fully custom radio group that still uses the Tamagui styling system. You provide your own styled components and get back a fully functional radio group component.
import { createRadioGroup, RadioGroupContext } from '@tamagui/radio-group'
import { styled, View } from 'tamagui'
const CustomFrame = styled(View, {
// your styles
})
const CustomItem = styled(View, {
context: RadioGroupContext,
// your styles
})
const CustomIndicator = styled(View, {
context: RadioGroupContext,
// your styles
})
export const CustomRadioGroup = createRadioGroup({
Frame: CustomFrame,
Item: CustomItem,
Indicator: CustomIndicator,
})
</Tabs.Content>
<Tabs.Content value="headless">
The @tamagui/radio-headless package provides three hooks for building custom radio groups:
useRadioGroup - for the container/groupuseRadioGroupItem - for individual radio itemsuseRadioGroupItemIndicator - 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>
)
}
</Tabs.Content>
<Tabs.Content value="styled">
RadioGroup extends Stack views inheriting all the Tamagui standard props, plus:
</Tabs.Content> <Tabs.Content value="unstyled">
When using createRadioGroup, you provide styled components:
Frame: The root container componentItem: The radio item componentIndicator: The indicator component</Tabs.Content> <Tabs.Content value="headless">
The useRadioGroup hook accepts these options:
</Tabs.Content>
<PropsTable
data={[
{
name: 'name',
type: 'string',
description: Equivalent to input name attribute.,
},
{
name: 'value',
type: 'string',
description: Controlled value of the selected radio item.,
},
{
name: 'defaultValue',
type: 'string',
description: Default value for uncontrolled usage.,
},
{
name: 'required',
type: 'boolean',
description: Sets aria-required attribute.,
},
{
name: 'disabled',
type: 'boolean',
description: Sets aria-disabled on web, and disables touch on native for all children items.,
},
{
name: 'native',
type: 'boolean',
description: Renders native radio button on web.,
default: 'false',
},
{
name: 'onValueChange',
type: '(value: string) => void',
description: 'Called when the selected value changes.',
},
{
name: 'orientation',
type: '"horizontal" | "vertical"',
description: 'Orientation of the radio group.',
},
{
name: 'accentColor',
type: 'string',
description: 'Sets accent-color style when native prop is enabled.',
},
]}
/>
<Tabs.Content value="styled">
<PropsTable
data={[
{
name: 'labeledBy',
type: 'string',
description: Sets aria-labelledby on web.,
},
{
name: 'value',
type: 'string',
description: Input value for the radio button.,
},
{
name: 'disabled',
type: 'boolean',
description: Sets aria-disabled on web, and disables touch on native.,
},
{
name: 'id',
type: 'string',
description: ID used on the web.,
},
{
name: 'scaleSize',
type: 'number',
default: '0.5',
description: Scale factor for the radio item size. Tamagui size tokens map to button heights, so you typically want smaller radio buttons.,
},
{
name: 'unstyled',
type: 'boolean',
default: 'false',
description: When true, removes all default Tamagui styling.,
},
]}
/>
RadioGroup.Indicator appears only when the parent Item is checked. It extends ThemeableStack, getting Tamagui standard props adding:
<PropsTable
data={[
{
name: 'unstyled',
required: false,
type: boolean,
description: Removes all default Tamagui styles.,
},
]}
/>
</Tabs.Content>
<Tabs.Content value="headless">
| Property | Type | Description |
|---|---|---|
providerValue | RadioGroupContextValue | Value to pass to your context provider |
frameAttrs | object | Props to spread on the group container (role, aria-orientation, etc.) |
rovingFocusGroupAttrs | object | Props for roving focus behavior (optional) |
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: () => {},
})
| Property | Type | Description |
|---|---|---|
checked | boolean | Whether this item is currently selected |
providerValue | object | Context value for the indicator |
frameAttrs | object | Props to spread on the item element |
bubbleInput | ReactNode | Hidden input for form compatibility |
native | boolean | Whether using native radio |
isFormControl | boolean | Whether inside a form |
Hook for the visual indicator of checked state.
const { checked, ...dataAttrs } = useRadioGroupItemIndicator({
radioGroupItemContext: YourRadioGroupItemContext,
disabled: false,
})
| Property | Type | Description |
|---|---|---|
checked | boolean | Whether the parent item is checked |
data-state | string | 'checked' or 'unchecked' |
data-disabled | string | undefined | Present when disabled |
</Tabs.Content>
</Tabs>