# Search bar

> An iOS-style search field: focusing it shrinks the field while a Cancel button springs in beside it and scope chips cascade in below. Clear button, loading spinner and debounced queries built in.

- Type: Component (inputs)
- Access: Free, open source (MIT)
- Page: https://goodui.dev/native/components/search-bar
- Markdown: https://goodui.dev/native/components/search-bar.md
- Registry item: https://goodui.dev/native/r/search-bar.json
- Dependencies: react-native-reanimated, react-native-svg
- Keywords: react native search bar, expo search input, ios search field cancel button, search with scope bar, debounced search input, filter chips search

## When to use

- Searching a list or the whole app: contacts, messages, products, transactions.
- Search that hits a server: set debounce and drive loading while results load.
- Search that can be narrowed to a category: pass scopes.

## When not to use

- Form fields: use text-field.
- Switching views of the same data without typing: use segmented-control.
- A single always-visible filter row without a query: use chips or a segmented control.

## Installation

With the shadcn CLI (needs a components.json in the Expo app), then `npx expo install --fix` so native dependencies match your Expo SDK. The foundation comes along the first time.

```bash
npx shadcn@latest add https://goodui.dev/native/r/search-bar.json
npx expo install --fix
```

The component lands in `components/goodui-native/search-bar/` and imports the foundation from `components/goodui-native/foundation/` with relative paths; it also installs locale.

Built-in strings follow `<GooduiLocaleProvider>` (one [language pack](https://goodui.dev/native/r/locale-tr.json) translates the whole kit); the `labels` prop overrides them for one instance.

## Usage

```tsx
import { useState } from "react";
import { SearchBar } from "@/components/goodui-native/search-bar/search-bar";

export function ContactSearch() {
  const [query, setQuery] = useState("");
  const [scope, setScope] = useState<"all" | "people" | "teams">("all");
  return (
    <SearchBar
      placeholder="Search contacts"
      debounce={250}
      onChangeText={setQuery}
      scopes={[
        { value: "all", label: "All" },
        { value: "people", label: "People" },
        { value: "teams", label: "Teams" },
      ]}
      scope={scope}
      onScopeChange={setScope}
    />
  );
}
```

## Examples

### Server search with a spinner

```tsx
const [loading, setLoading] = useState(false);

<SearchBar
  placeholder="Search transactions"
  debounce={300}
  loading={loading}
  onChangeText={async (q) => {
    setLoading(true);
    setResults(await api.search(q));
    setLoading(false);
  }}
/>
```

### Navigation bar size without Cancel

```tsx
<SearchBar size="sm" showCancel={false} placeholder="Filter" onChangeText={setFilter} />
```

## API reference

### SearchBar

`import { SearchBar } from "@/components/goodui-native/search-bar/search-bar";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `string` | – | Query text (controlled). With `debounce`, the field keeps typing smoothly and syncs when this changes from outside. |
| `defaultValue` | `string` | `""` | Initial query when uncontrolled. |
| `onChangeText` | `(text: string) => void` | – | Called with the query. Debounced when `debounce` is set; clear, cancel and submit always call it right away. |
| `debounce` | `number` | `0` | Wait this many ms after the last keystroke before calling `onChangeText`. Default 0 (every keystroke). |
| `onSubmit` | `(text: string) => void` | – | Called when the search key is pressed, with the current query. |
| `onCancel` | `() => void` | – | Called after Cancel is pressed (the field has already blurred and, by default, cleared). |
| `onActiveChange` | `(active: boolean) => void` | – | Called when the field becomes active (focused or holding a query) or inactive. |
| `placeholder` | `string` | – | Placeholder. Same as `labels.placeholder`. |
| `cancelLabel` | `string` | – | Label of the cancel button. Same as `labels.cancel`. |
| `showCancel` | `boolean` | `true` | Reveal a Cancel button while the field is active. Default true. |
| `clearOnCancel` | `boolean` | `true` | Cancel also clears the query. Default true. |
| `scopes` | `SearchScope<T>[]` | – | Scope chips under the field, for example All, People, Messages. |
| `scope` | `T` | – | Selected scope value. |
| `onScopeChange` | `(value: T) => void` | – | Called with the tapped scope value. |
| `scopesVisible` | `"active" \| "always"` | `"active"` | `active` reveals the chips only while searching (like iOS); `always` keeps them visible. |
| `loading` | `boolean` | `false` | Shows a spinner in place of the magnifier, for example while results load. |
| `variant` | `"filled" \| "outline"` | `"filled"` | `filled` is a muted surface; `outline` adds a hairline border for plain backgrounds. |
| `size` | `"sm" \| "md"` | `"md"` | Heights 36 (`sm`, navigation bars) and 44 pt (`md`). |
| `autoFocus` | `boolean` | `false` | Focus the field on mount. |
| `haptic` | `boolean` | `true` | `false` silences this bar's haptics (cancel, clear, scope change). |
| `inputRef` | `Ref<ComponentRef<typeof TextInput>>` | – | Ref to the underlying TextInput. |
| `accessibilityLabel` | `string` | – | Screen reader label. Defaults to the placeholder. |
| `labels` | `Partial<SearchBarLabels>` | – | Overrides for the built-in strings; a GooduiLocaleProvider pack sets them app-wide. |
| `style` | `StyleProp<ViewStyle>` | – | Style for the outer container. |
| `testID` | `string` | – | Identifier for end-to-end tests. |

## Accessibility

- The TextInput has accessibilityRole search and is labelled by accessibilityLabel or the placeholder.
- Clear is a button labelled 'Clear search'; Cancel is a button with its visible label. Both are hidden from screen readers while they are not shown.
- Scope chips are tabs in a tablist with their selected state. The chip row is hidden from screen readers while collapsed.
- The search key submits (returnKeyType and enterKeyHint are search).

## Motion

- Focus: the field gives up exactly the room Cancel needs while Cancel slides in from the edge, both on the morph spring.
- The focus ring fades to the accent color.
- Scope chips: the row's height springs open and the chips cascade in 35 ms apart on the snappy spring; they fade out together when the bar deactivates.
- Clear scales in when there is text; chips scale down on press.
- Reduced motion: Cancel and the chip row appear with a short fade-speed timing instead of travelling, chips do not cascade or scale.

## Haptics

- tap on Cancel and on clear
- select when a different scope is chosen

## Performance

- Layout changes (field width, chip row height) animate on the UI thread.
- With debounce, typing never re-renders the parent per keystroke; onChangeText fires after the pause.

## Notes for AI

- Set debounce (ms) instead of writing your own debounce around onChangeText. Clear, Cancel and submit always fire immediately.
- It works uncontrolled; pass value only to set the query from outside (a tapped recent search). With debounce, the field ignores its own echoed value.
- Cancel blurs and, by default, clears. Use clearOnCancel={false} to keep the query.
- scopesVisible='active' shows chips only while searching like iOS; use 'always' for a persistent filter.
- Inside a ScrollView set keyboardShouldPersistTaps='handled' so clear and chips work while the keyboard is up.

## Related

- [Text field](https://goodui.dev/native/components/text-field.md): A text field whose label glides up as you focus it, with helper and error text that slide in, a shake and haptic on error, clear and reveal buttons, a character counter and a multiline mode that grows with its text.
- [Segmented control](https://goodui.dev/native/components/segmented-control.md): Two to five views of the same thing with an elastic highlight that stretches toward the new segment, can be dragged, and ticks at every boundary. Includes a billing toggle.

## Guidance for AI tools

Search bar: An iOS-style search field: focusing it shrinks the field while a Cancel button springs in beside it and scope chips cascade in below. Clear button, loading spinner and debounced queries built in. Use only the documented props. Keep accessibility, reduced motion and both color schemes intact when adapting it.

Full library index: https://goodui.dev/native/llms.txt
