Skip to main content

SearchBar

The search bar lets users enter a query to find content. Follows the Material Design 3 Search specification.

MD3 Search has two parts: the search bar and the search view, the panel that opens from the bar and shows suggestions. This component is the search bar. SearchView is the view.

Usage​

import { SearchBar } 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 [query, setQuery] = useState('')
return (
<SafeAreaProvider>
<ThemeProvider iconResolver={mdiResolver}>
<View style={{ flex: 1, justifyContent: 'center', padding: 16 }}>
<SearchBar
placeholder="Search mail"
value={query}
onChangeText={setQuery}
onSearch={(text) => console.log('search', text)}
/>
</View>
</ThemeProvider>
</SafeAreaProvider>
)
}

The bar shows a clear button while it holds text. The clear button empties the bar through onChangeText(''), calls onClear, and moves focus back to the input. Set showClearButton={false} to remove it.

The keyboard shows a Search key. When the user presses it, onSearch receives the current query.

Leading navigation icon​

The leading icon is a search icon by default, and it is decoration. Set onLeadingIconPress to make it a button, for example a menu or a back arrow. Give the button a name with leadingIconAccessibilityLabel.

import { SearchBar } 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, justifyContent: 'center', padding: 16 }}>
<SearchBar
placeholder="Search"
leadingIcon="menu"
onLeadingIconPress={() => console.log('open drawer')}
leadingIconAccessibilityLabel="Open menu"
/>
</View>
</ThemeProvider>
</SafeAreaProvider>
)
}

Opening a search view​

Set onPress to make the bar a button that opens a SearchView. The bar is then one tab stop with the button role. A press, Enter, or Space calls onPress. The input is read-only and out of the tab order, but it still shows value and the placeholder, so the bar shows the query after the view closes. The clear button and the trailing actions stay buttons of their own.

<SearchBar placeholder="Search" value={query} onPress={() => setOpen(true)} />

Use onPress rather than onFocus for this. The view returns focus to the bar when it closes, and a bar that opens on focus would reopen it.

Trailing slot​

The trailing slot takes one of two props:

PropUse it for
actionsIcon buttons, such as a voice action. Each entry needs an accessibilityLabel.
trailingCustom content that actions cannot build, such as an Avatar. MD3 sizes the avatar at 30dp.

The two props fill the same slot, so the type rejects both together.

import { Avatar, SearchBar } 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, justifyContent: 'center', padding: 16, gap: 16 }}>
<SearchBar
placeholder="Search"
actions={[
{
icon: 'microphone',
accessibilityLabel: 'Voice search',
onPress: () => console.log('listen'),
},
]}
/>
<SearchBar
placeholder="Search"
trailing={<Avatar size="extraSmall" label="JD" />}
/>
</View>
</ThemeProvider>
</SafeAreaProvider>
)
}

Density​

The MD3 bar is 56dp high. In a desktop toolbar next to 32dp or 40dp controls, pass a negative density. Each step on the Material density scale removes 4dp, so -4 gives a 40dp bar. The icon buttons keep their 40dp frame and the text keeps bodyLarge, so -4 is the floor.

import { SearchBar } 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, justifyContent: 'center', padding: 16, gap: 16 }}>
<SearchBar placeholder="Default, 56dp" />
<SearchBar placeholder="Dense, 40dp" density={-4} />
</View>
</ThemeProvider>
</SafeAreaProvider>
)
}

style={{ height }} does not do this. The style prop goes to the root view, and the pill inside keeps its own height.

Disabled​

import { SearchBar } 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, justifyContent: 'center', padding: 16 }}>
<SearchBar placeholder="Search" disabled />
</View>
</ThemeProvider>
</SafeAreaProvider>
)
}

A disabled bar keeps its container color. Its text, placeholder, and icons change to 38% onSurface.

Specification​

PartMD3 tokenValue
Containerheight, shape56dp, full corner. density removes 4dp per step, to 40dp at -4
ContainercolorsurfaceContainerHigh
Containerelevationlevel 0
Containermaximum width720dp — change it with style={{ maxWidth }}
Leading iconcoloronSurface
Trailing iconscoloronSurfaceVariant
Input texttype, colorbodyLarge, onSurface
Placeholdertype, colorbodyLarge, onSurfaceVariant
Focus indicatorcolorsecondary

The bar shows a hover layer. It shows a focus layer and a focus ring for keyboard focus only. A press moves focus into the input and shows no press layer, as MD3 specifies.

MD3 also sets a minimum width of 360dp. The component does not apply it, because the bar would then overflow a phone screen that is narrower than 360dp plus its margins.

Accessibility​

  • The bar is a search landmark on web, and the input has the searchbox role.
  • The placeholder becomes the accessible name of the input. Set accessibilityLabel to use a different name.
  • A decorative leading icon stays out of the accessibility tree. When onLeadingIconPress makes it a button, name it with leadingIconAccessibilityLabel. In development, the component warns when that label is missing.
  • The press wrapper is not a tab stop, so the bar costs a keyboard user one stop, plus one for the clear button while the bar holds text.
  • With onPress, the bar is the one stop, a button named by the placeholder or accessibilityLabel, and the input leaves the accessibility tree.

Props​

PropTypeDefaultRequiredDescription
leadingIconIconSourcemagnifyNoIcon rendered at the start of the bar. 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 }`.
onLeadingIconPress() => void-NoMakes the leading icon a button — for a navigation icon such as a menu or a back arrow. Label it with `leadingIconAccessibilityLabel`.
leadingIconAccessibilityLabelstring-NoAccessibility label for the leading icon when it is a button.
onSearch(query: string) => void-NoCalled with the current query when the user submits from the keyboard.
onPress() => void-NoMakes the bar a button, for a bar that opens a `SearchView`. The bar is then one tab stop with the `button` role, a press or Enter calls this, and the input is read-only and out of the tab order. It still shows `value` and the placeholder. The clear button and the trailing actions stay buttons of their own.
densityenum0NoDensity of the bar, on the Material density scale. `0` is the MD3 bar at 56dp. Each step below removes 4dp: `-1` is 52dp, `-2` is 48dp, `-3` is 44dp, and `-4` is 40dp, the frame of the icon buttons, so it is the floor. Use a negative density in a desktop toolbar with small controls. Text and icon sizes do not change.
showClearButtonbooleanNoShows a clear button while the bar holds text.
clearButtonAccessibilityLabelstringClear searchNoAccessibility label for the clear button.
onClear() => void-NoCalled after the clear button empties the bar.
disabledbooleanNoDisables the input and every button in the bar.
containerColorstring-NoOverride the container (background) color. Hover and focus state-layer colors are derived from it. Disabled state keeps the standard appearance.
contentColorstring-NoOverride the content (input text and icon) color. Disabled state keeps the standard appearance.
inputStyleStyleProp<TextStyle>-NoAdditional style applied to the text input element.
styleStyleProp<ViewStyle>-NoStyle applied to the root container. The MD3 maximum width is 720 dp — set `maxWidth` here to change it.
refRef<TextInput>-NoRef to the inner `TextInput`, for imperative `focus()`, `blur()` and `clear()` calls.
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: { nativeEvent: { key?: string; }; }) => void-No-
aria-invalidboolean-No-
aria-describedbystring-No-
actionsSearchBarAction[]-NoIcon buttons rendered in the trailing slot, such as a voice action.
trailingReactNode-NoCustom trailing content, such as an `Avatar`. Use it instead of `actions` when the slot needs something `actions` cannot build.