Skip to main content

Radio

Radio buttons allow users to select one option from a set of mutually exclusive choices. Follows the Material Design 3 Radio button specification.

Usage​

import { Radio } from '@rootnative/components'
import { mdiResolver } from '@rootnative/components/mdi'
import { ThemeProvider } from '@rootnative/core'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { useState } from 'react'
import { View } from 'react-native'

export default function App() {
const [selected, setSelected] = useState('a')
return (
<SafeAreaProvider>
<ThemeProvider iconResolver={mdiResolver}>
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center', gap: 12 }}>
<Radio value={selected === 'a'} onValueChange={() => setSelected('a')} />
<Radio value={selected === 'b'} onValueChange={() => setSelected('b')} />
</View>
</ThemeProvider>
</SafeAreaProvider>
)
}
Controlled or uncontrolled

Pass value with onValueChange to own the state, as above. Omit value and Radio manages its own, starting from defaultValue:

<Radio defaultValue onValueChange={handleChange} />

onValueChange still fires either way. In earlier prereleases, passing onValueChange without value did nothing visually — the callback fired but the control never moved. See the changelog if you are upgrading.

A radio is select-only, so an uncontrolled one latches: it turns itself on and only a controlling parent can turn it back off. Radio groups are the controlled case — see Radio Group below.

Radio Group​

Build a radio group by controlling which Radio has value={true}:

import { Radio } from '@rootnative/components'
import { mdiResolver } from '@rootnative/components/mdi'
import { ThemeProvider } from '@rootnative/core'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { useState } from 'react'
import { View } from 'react-native'

export default function App() {
const [choice, setChoice] = useState('option1')
return (
<SafeAreaProvider>
<ThemeProvider iconResolver={mdiResolver}>
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center', gap: 12 }}>
<Radio value={choice === 'option1'} onValueChange={() => setChoice('option1')} />
<Radio value={choice === 'option2'} onValueChange={() => setChoice('option2')} />
<Radio value={choice === 'option3'} onValueChange={() => setChoice('option3')} />
</View>
</ThemeProvider>
</SafeAreaProvider>
)
}

Disabled​

import { Radio } from '@rootnative/components'
import { mdiResolver } from '@rootnative/components/mdi'
import { ThemeProvider } from '@rootnative/core'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { View } from 'react-native'

export default function App() {
return (
<SafeAreaProvider>
<ThemeProvider iconResolver={mdiResolver}>
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center', gap: 12 }}>
<Radio value={false} onValueChange={() => {}} disabled />
<Radio value onValueChange={() => {}} disabled />
</View>
</ThemeProvider>
</SafeAreaProvider>
)
}

In a list row​

To let the full ListItem toggle the radio, put onPress on the row and give the radio value with no onValueChange. The radio then only shows the state, and the row takes the press. See Selection controls in a row.

Props​

PropTypeDefaultRequiredDescription
valueboolean-NoWhether the radio button is selected (controlled). Pass this together with `onValueChange` to own the state. Omit it and the component manages its own state, starting from `defaultValue`.
defaultValuebooleanNoInitial value when uncontrolled. Ignored once `value` is passed. A radio is select-only, so an uncontrolled one latches on: only a controlling parent can turn it back off.
onValueChange(value: boolean) => void-NoCallback fired when an unselected radio is pressed. Always receives `true` — radios are select-only, so pressing an already-selected radio is a no-op. With `value` set and no `onValueChange`, the radio only shows the state. It takes no press, leaves the tab order, and is hidden from assistive technology. Use this in a pressable `ListItem`, and give the row `role="radio"` and `aria-checked`. The row then carries the press and the state. A disabled radio is never hidden.
containerColorstring-NoOverride the outer ring and inner dot color when selected. State-layer colors (hover, press) are derived automatically.
contentColorstring-NoOverride the outer ring color when unselected.
styleStyleProp<ViewStyle>-NoStyle applied to the root container. Static form only — the function form `(state) => style` is not supported because the component drives its container background through Reanimated. Use `containerColor` / `contentColor` for state-aware styling.
hrefAttrs{ target?: string; rel?: string; download?: string | boolean; }-NoWeb only. The `target`, `rel` and `download` of the `<a>` that a view with an `href` renders, for example under Expo Router's `<Link asChild>`. `target: '_blank'` opens the link in a new tab. Native ignores it.
onKeyDown(event: PressableKeyDownEvent) => void-No-