A text input for search queries, with a search icon, a clear button, Escape to clear and an optional keyboard shortcut hint. Works for live filtering, submit-on-Enter, async search and native search forms.
Use value and onChange to filter as the user types, or onSubmit to run the search when they press Enter. The clear button and Esc reset the value and call onClear.
onSubmit replaces form submission
When onSubmit is set, Enter calls it and does not submit a
surrounding <form>. Leave onSubmit off (and give the field a name) when
you want Enter to submit the form natively.
<SearchField> {/* root: <div>, owns the query and state attributes */} <Label /> {/* from `label` */} <FieldGroup> {/* the visible box */} <SearchIcon /> {/* decorative */} <FieldInput /> {/* <input type="search"> */} <Keyboard /> {/* from `shortcut`, only while empty */} <Button /> {/* clear button, only while not empty */} </FieldGroup> <Description /> {/* from `description` */} <FieldError /> {/* shown only while invalid */}</SearchField>
Part
Renders
Notes
SearchField
<div>
Root. Carries data-slot="search-field", the group/field class and data-empty while the query is empty.
Label
<label>
Rendered when label is set.
FieldGroup
<div>
The visible box, styled by fieldVariants.
Search icon
<svg>
Decorative (aria-hidden).
FieldInput
<input type="search">
The native input. The browser's own cancel button is hidden in favour of the clear button.
Shortcut
<kbd>
Rendered when shortcut is set, visible only while the field is empty. It's a hint; you bind the key.
Clear button
<button>
Labelled "Clear search", skipped in the tab order, hidden while empty. React Aria wires it through context, so don't place other React Aria buttons inside the field.
shortcut shows a hint while the field is empty. Binding the key is up to you: listen on window and focus the input. The root doesn't expose the input, so reach it through a wrapper ref.
⌘K
Press ⌘K (or Ctrl K) anywhere on the page to focus the field.
onSubmit runs on Enter with the current query, and onClear runs when the clear button or Esc empties the field. Set enterKeyHint="search" to label the enter key on mobile keyboards.
Control the value with value and onChange to filter a list as the user types. React Aria's useFilter gives you locale-aware contains, startsWith and endsWith matchers that ignore case and accents with sensitivity: "base". Announce the result count with an aria-live region.
Fetch results in an effect keyed on the query and abort stale requests, so fast typing never shows out-of-order results. Show a Spinner while loading and an empty state when nothing matches.
Without onSubmit, Enter submits the surrounding form natively and the query is sent under its name, like a classic ?q= search. isRequired and minLength use native validation. Give the form role="search" so it's exposed as a search landmark.
import { useState } from "react";import { Form } from "react-aria-components";import { Button } from "@/components/ui/button";import { SearchField } from "@/components/ui/search-field";export default function SearchFieldForm() { const [submitted, setSubmitted] = useState<string | null>(null); return ( <Form role="search" className="flex w-full max-w-sm flex-col gap-3" onSubmit={(e) => { e.preventDefault(); const q = String(new FormData(e.currentTarget).get("q")); setSubmitted(`/search?q=${encodeURIComponent(q)}`); }} > <div className="flex items-start gap-2"> <SearchField aria-label="Search the help center" name="q" isRequired minLength={2} placeholder="How do I export data?" className="flex-1" /> <Button type="submit">Search</Button> </div> {submitted && ( <code className="self-start rounded-md bg-muted px-2 py-1 text-xs"> {submitted} </code> )} </Form> );}
The input has type="search", so assistive technology announces it as a search field.
Always provide a label: label, aria-label, or aria-labelledby. The search icon is decorative.
The clear button is labelled "Clear search" (localized). It's skipped in the tab order because Esc does the same thing, and pressing it keeps focus in the input so the mobile keyboard stays open.
The shortcut hint is visual only. If you bind a global shortcut, avoid keys users type into other inputs; the header recipe ignores / while an input is focused.
Wrap search areas in role="search" (on a <form> or a <div>) to create a search landmark.
For live results, announce counts or "no results" with an aria-live="polite" region.
On the SearchField root. Children can react with group-data-*/field: variants; the component itself uses group-data-empty/field: to swap the shortcut hint and the clear button.
Attribute
Present when
data-empty
The query is empty
data-disabled
isDisabled is true
data-readonly
isReadOnly is true
data-required
isRequired is true
data-invalid
Validation failed, or isInvalid is true
On the FieldGroup: data-hovered, data-focus-within, data-focus-visible, data-invalid and data-disabled.
The box comes from fieldVariants in field.tsx; see Text Field. className also accepts a function of the render state (isEmpty, isDisabled, isInvalid, isReadOnly, isRequired, state).