Back to Tamagui

RadioGroup

code/tamagui.dev/data/docs/components/radio-group/2.0.0.mdx

1.144.411.2 KB
Original Source
<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

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

tsx

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

tsx

</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, 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, 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">

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)

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: () => {},
})
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

useRadioGroupItemIndicator

Hook for the visual indicator of checked state.

tsx
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

</Tabs.Content>

</Tabs>