Skip to main content

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>
)
}
Controlled or uncontrolled

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​

PropTypeDefaultRequiredDescription
valueboolean-NoWhether 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`.
defaultValuebooleanNoInitial value when uncontrolled. Ignored once `value` is passed.
onValueChange(value: boolean) => void-NoCallback 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.
selectedIconIconSource-NoIcon 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.
unselectedIconIconSource-NoIcon shown on the thumb when unselected. When provided, the thumb renders at the larger selected size. Same accepted forms as `selectedIcon`.
containerColorstring-NoOverride the track color. State-layer colors (hover, press) are derived automatically.
contentColorstring-NoOverride the thumb and icon color.
styleStyleProp<ViewStyle>-NoStyle 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; }-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-