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>
)
}
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
| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
value | boolean | - | No | Whether 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`. |
defaultValue | boolean | | No | Initial 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 | - | No | Callback 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. |
containerColor | string | - | No | Override the outer ring and inner dot color when selected. State-layer colors (hover, press) are derived automatically. |
contentColor | string | - | No | Override the outer ring color when unselected. |
style | StyleProp<ViewStyle> | - | No | Style 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; } | - | No | Web 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 | - |