---
title: RadioGroup
description: Use in a form to allow selecting one option from multiple
name: radio-group
component: RadioGroup
package: radio-group
demoName: RadioGroup
---

<YStack className="is-sticky" />

<Tabs id="type" defaultValue="styled">
<Tabs.List>
  <TooltipSimple label="With Tamagui's default styles">
    <Tabs.Tab value="styled">Styled</Tabs.Tab>
  </TooltipSimple>
  <TooltipSimple label="No default styles, easy to customize">
    <Tabs.Tab value="unstyled">Unstyled</Tabs.Tab>
  </TooltipSimple>
  <TooltipSimple label="No dependency on Tamagui's core">
    <Tabs.Tab value="headless" label="No styles and no dependency on Tamagui's styling">Headless</Tabs.Tab>
  </TooltipSimple>
</Tabs.List>

<HeroContainer>
  <Tabs.Content value="styled" justifyContent="center" alignItems="center" width="100%">
    <RadioGroupDemo />
  </Tabs.Content>
  <Tabs.Content value="unstyled" justifyContent="center" alignItems="center" width="100%">
    <RadioGroupDemo />
  </Tabs.Content>
  <Tabs.Content value="headless" justifyContent="center" alignItems="center" width="100%">
    <RadioGroupHeadlessDemo />
  </Tabs.Content>
</HeroContainer>

<Tabs.Content value="styled">
```tsx hero template=RadioGroup

````
</Tabs.Content>
<Tabs.Content value="unstyled">
```tsx hero template=RadioGroup

````

</Tabs.Content>
<Tabs.Content value="headless">
```tsx hero template=RadioGroupHeadless

````
</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.`,
  ]}
/>

## Installation

<Tabs.Content value="styled">
RadioGroup is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/radio-group
````

</Tabs.Content>

<Tabs.Content value="unstyled">
RadioGroup is already installed in `tamagui`, or you can install it independently:

```bash
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.

```bash
npm install @tamagui/radio-headless
```

</Tabs.Content>

## Usage

<Tabs.Content value="styled">

```tsx
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.

```tsx
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/group
- `useRadioGroupItem` - for individual radio items
- `useRadioGroupItemIndicator` - for the indicator (checked state visual)

### Basic Usage

```tsx
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>

## API Reference

### RadioGroup

<Tabs.Content value="styled">

RadioGroup extends Stack views inheriting all the [Tamagui standard props](/docs/intro/props), plus:

</Tabs.Content>
<Tabs.Content value="unstyled">

When using `createRadioGroup`, you provide styled components:

- `Frame`: The root container component
- `Item`: The radio item component
- `Indicator`: 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">

### RadioGroup.Item

<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

RadioGroup.Indicator appears only when the parent Item is checked. It extends [ThemeableStack](/docs/components/stacks#themeablestack), getting [Tamagui standard props](/docs/intro/props) adding:

<PropsTable
  data={[
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>

</Tabs.Content>

<Tabs.Content value="headless">

### useRadioGroup Return Value

| 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)                            |

### useRadioGroupItem

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

```tsx
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                   |

### useRadioGroupItemIndicator

Hook for the visual indicator of checked state.

```tsx
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>
