Search Field
betaA search box with the same sizes as Input. A clear button shows once there is text, Esc clears it, and an optional count says how many matched.
Preview
count
size
states
Install
1. Add the registry to
components.json, once per app. Without it, the shadcn CLI looks up@txbrendanin a public list that txbrendan/ui does not control.npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.
npx shadcn@latest add @txbrendan/theme3. Add the component.
npx shadcn@latest add @txbrendan/search-field
Use it for
- Filtering a list or a table on the same page as people type.
- Give it a name: aria-label, like "Search people", or an id that a visible label's htmlFor points at.
- Pass count with the number of matches. It shows in the field, like "3 people" or "No people", and is announced politely. Set noun to the singular and plural words.
- Pass shortcut to focus the field from anywhere on the page: one key, like "/", or "Mod+K" for ⌘K on a Mac and Ctrl+K elsewhere. It shows as a quiet key at the end of the empty field, and hides once people type, on touch screens, and while disabled.
- Esc clears the text. With the field empty, Esc leaves it, and goes on to any dialog around it.
- Use onEnter to run a search that waits for Enter, like one that asks a server.
Avoid it for
- Giving two parts on one page the same shortcut. Command Menu and Rail's onSearch listen for ⌘K too, so pass "Mod+K" only where neither is on the page.
- Picking one value from a list. Use a combobox.
- Using the placeholder as the name. It disappears as soon as people type.
- A search that opens a results page. A plain input in a form with a submit button works better there.
Props
- value
The typed text. Use it with onValueChange.
string
- defaultValue
The text at first render, when you do not control it.
string
- onValueChange
Called with the new text on every change, and with "" when cleared.
(value: string) => void
- onEnter
Called with the text when Enter is pressed.
(value: string) => void
- size
Height, padding, and text size. The same heights as Input.
"sm" | "md" | "lg" · default "md"
- count
How many matched. Shows in the field and is announced. Hidden while the field is empty.
number | null
- noun
The singular and plural words for the count.
[string, string] · default ["match", "matches"]
- shortcut
Focuses the field from anywhere. One key, like "/", is ignored while people type in another field. "Mod+" and one key, like "Mod+K", is ⌘K on a Mac and Ctrl+K elsewhere, and works from another field too, but not in a rich text editor. Shows as a ghost Kbd while the field is empty, with a letter in capitals, as the keyboard prints it.
string
- disabled
Blocks typing and hides the clear button.
boolean · default false
- placeholder
A hint inside the empty field, like "Search people".
string
- aria-label
Names the field. Required unless you pass aria-labelledby, or an id with a visible label.
string
- className
Classes for the field's box. Other props, like name and id, land on the input.
string
| Prop | Type | Default | What it does |
|---|---|---|---|
| value | string | - | The typed text. Use it with onValueChange. |
| defaultValue | string | - | The text at first render, when you do not control it. |
| onValueChange | (value: string) => void | - | Called with the new text on every change, and with "" when cleared. |
| onEnter | (value: string) => void | - | Called with the text when Enter is pressed. |
| size | "sm" | "md" | "lg" | "md" | Height, padding, and text size. The same heights as Input. |
| count | number | null | - | How many matched. Shows in the field and is announced. Hidden while the field is empty. |
| noun | [string, string] | ["match", "matches"] | The singular and plural words for the count. |
| shortcut | string | - | Focuses the field from anywhere. One key, like "/", is ignored while people type in another field. "Mod+" and one key, like "Mod+K", is ⌘K on a Mac and Ctrl+K elsewhere, and works from another field too, but not in a rich text editor. Shows as a ghost Kbd while the field is empty, with a letter in capitals, as the keyboard prints it. |
| disabled | boolean | false | Blocks typing and hides the clear button. |
| placeholder | string | - | A hint inside the empty field, like "Search people". |
| aria-label | string | - | Names the field. Required unless you pass aria-labelledby, or an id with a visible label. |
| className | string | - | Classes for the field's box. Other props, like name and id, land on the input. |
Accessibility
- A native input with type="search", so screen readers call it a search field.
- Its type needs a name: aria-label, aria-labelledby, or an id for a label's htmlFor.
- The count sits in a polite live region, so a new count is read out without moving focus.
- Esc clears the text first, and marks the field data-owns-escape while it has text. A dialog around it closes only on the next Esc.
- The clear button has the name "Clear search". It is not a Tab stop, since Esc does the same from the keyboard. It is at least 24px square, and 44px on a coarse pointer.
- shortcut sets aria-keyshortcuts, so screen readers can say the key. "Mod+K" sets "Meta+K Control+K". The printed key is aria-hidden, so it is not read twice.
- On touch screens the text is 16px, so iOS Safari does not zoom in when the field gets focus.
Examples
Jump in with ⌘K
<SearchField aria-label="Search the app" placeholder="Search" shortcut="Mod+K" />Filter a list
const people = ["Dan Kerr", "Maya Patel", "Tom Hale"];
const [query, setQuery] = useState("");
const shown = people.filter((name) => name.toLowerCase().includes(query.toLowerCase()));
<SearchField
aria-label="Search people"
placeholder="Search people"
value={query}
onValueChange={setQuery}
count={shown.length}
noun={["person", "people"]}
shortcut="/"
/>Small, in a toolbar
<SearchField size="sm" aria-label="Search matches" placeholder="Search" />