Switch
Switches toggle the state of a single item on or off. Follows the Material Design 3 Switch specification.
Usage
import { Switch } 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 [isOn, setIsOn] = useState(false)
return (
<SafeAreaProvider>
<ThemeProvider iconResolver={mdiResolver}>
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Switch value={isOn} onValueChange={setIsOn} />
</View>
</ThemeProvider>
</SafeAreaProvider>
)
}
Pass value with onValueChange to own the state, as above. Omit value and
Switch manages its own, starting from defaultValue:
<Switch 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.
With Icons
Icons are opt-in — the MD3 default switch renders none. Pass selectedIcon (and optionally unselectedIcon) for the spec's "switch with icon" variant.
import { Switch } 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 [a, setA] = useState(true)
const [b, setB] = useState(true)
return (
<SafeAreaProvider>
<ThemeProvider iconResolver={mdiResolver}>
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center', gap: 16 }}>
<Switch value={a} onValueChange={setA} selectedIcon="check" />
<Switch
value={b}
onValueChange={setB}
selectedIcon="bell"
unselectedIcon="bell-off"
/>
</View>
</ThemeProvider>
</SafeAreaProvider>
)
}
When unselectedIcon is provided, the thumb renders at the larger selected size in both states.
Disabled
import { Switch } 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 }}>
<Switch value={false} onValueChange={() => {}} disabled />
<Switch value onValueChange={() => {}} disabled />
</View>
</ThemeProvider>
</SafeAreaProvider>
)
}
In a list row
To let the full ListItem toggle the switch, put onPress on the row and
give the switch value with no onValueChange. The switch 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 switch is toggled on (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. |
onValueChange | (value: boolean) => void | - | No | Callback fired when the switch is toggled. Receives the new value. With `value` set and no `onValueChange`, the switch 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="switch"` and `aria-checked`. The row then carries the press and the state. A disabled switch is never hidden. |
selectedIcon | IconSource | - | No | Icon shown on the thumb when selected. Accepts a string name (resolved via the theme's `iconResolver`, for example `mdiResolver`), a pre-rendered element, or a render function that receives `{ size, color }`. Opt-in: the MD3 default switch renders no icons — pass `'check'` for the spec's "switch with icon" variant. |
unselectedIcon | IconSource | - | No | Icon shown on the thumb when unselected. When provided, the thumb renders at the larger selected size. Same accepted forms as `selectedIcon`. |
containerColor | string | - | No | Override the track color. State-layer colors (hover, press) are derived automatically. |
contentColor | string | - | No | Override the thumb and icon color. |
style | StyleProp<ViewStyle> | - | No | Style for the switch. The keys that place it in its parent — `alignSelf`, `flex`, `flexGrow`, `flexShrink`, `flexBasis`, `width`, the margins, `position`, the insets and `zIndex` — go to an outer view, so the focus ring moves with the track. `minWidth` and `maxWidth` go with a `width`. The track keeps its MD3 size at the start edge of the outer view, because the thumb travel is fixed. Every other key styles the track. 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 | - |