ComponentsForm
Search field
A search box with a search icon, a clear button and an optional spinner, inside a search landmark.
Import
import { SearchField } from "@booleanpress/ui/search-field"Usage
SearchField is the library's Input in an InputGroup, inside a search landmark (role="search"). Enter calls onSearch with the text.
import { SearchField } from "@booleanpress/ui/search-field"
export function LogSearch({ onSearch }: { onSearch: (text: string) => void }) {
return <SearchField aria-label="Search delivery logs" placeholder="Search delivery logs" onSearch={onSearch} />
}It is uncontrolled with defaultValue, or controlled with value and onChange; filter as people type in onChange, or on Enter in onSearch. A ร button shows while there is text: it empties the field, calls onChange and keeps the focus in the field. Escape does the same; a second Escape, on the empty field, leaves it. loading shows a spinner at the end and marks the field busy while results load. size is sm (28 px), default (35 px) or lg (42 px), and variant="filled" fills the field grey; both default to the provider's controlSize and fieldVariant. className goes on the landmark round the field; the other attributes go on the input. It is not a form of its own, so it can sit inside one, such as a settings page: Enter searches and never sends that form, and with name the text is submitted with it.
Examples
Basic
A named search box with a placeholder.
With results count
The list filters as people type, and a status line under the field says how many match.
import { useState } from "react"
import { SearchField } from "@booleanpress/ui/search-field"
const MAILERS = ["Amazon SES", "Brevo", "Mailgun", "Postmark", "SendGrid", "SMTP2GO", "SparkPost", "Zoho Mail"]
export default function SearchFieldResultsCount() {
const [query, setQuery] = useState("")
const matches = MAILERS.filter((name) => name.toLowerCase().includes(query.trim().toLowerCase()))
return (
<div className="flex w-full max-w-sm flex-col gap-2">
<SearchField
aria-label="Search mailers"
placeholder="Search mailers"
value={query}
onChange={(event) => setQuery(event.target.value)}
aria-describedby="mailer-count"
/>
<p id="mailer-count" role="status" className="text-xs text-muted-foreground">
{matches.length} of {MAILERS.length} mailers
</p>
<ul className="flex flex-col gap-1 text-sm">
{matches.map((name) => (
<li key={name}>{name}</li>
))}
</ul>
</div>
)
}Loading
loading shows a spinner at the end; here it shows until you type, then for 800 ms after each change.
import { useEffect, useRef, useState } from "react"
import { SearchField } from "@booleanpress/ui/search-field"
export default function SearchFieldLoading() {
const [loading, setLoading] = useState(true)
const timer = useRef<ReturnType<typeof setTimeout>>(undefined)
useEffect(() => () => clearTimeout(timer.current), [])
return (
<div className="w-full max-w-sm">
<SearchField
aria-label="Search tickets"
placeholder="Search tickets"
defaultValue="invoice"
loading={loading}
onChange={() => {
// A pretend lookup: the results arrive 800 ms after the last change.
setLoading(true)
clearTimeout(timer.current)
timer.current = setTimeout(() => setLoading(false), 800)
}}
/>
</div>
)
}Sizes
sm is 28 px tall with 12 px text and icon, default 35 px with 14 px, lg 42 px with 16 px.
import { SearchField } from "@booleanpress/ui/search-field"
export default function SearchFieldSizes() {
return (
<div className="flex w-full max-w-sm flex-col gap-4">
<SearchField size="sm" aria-label="Search customers, small" placeholder="Small" />
<SearchField aria-label="Search customers, normal" placeholder="Normal" />
<SearchField size="lg" aria-label="Search customers, large" placeholder="Large" />
</div>
)
}Filled
variant="filled" draws the grey --field-filled fill, which stays on hover and focus.
Disabled
The text shows; the field and its clear button cannot be used.
Accessibility
- Semantics
- A
divwithrole="search"(a search landmark) round a nativeinput type="search"(asearchbox). The clear button is a nativebutton; the spinner is astatusnamed by the provider stringloading, and the input carriesaria-busywhile it shows. - Labels
- Name it with
aria-label, or aLabelwhosehtmlForis itsid. With neither, the field and the landmark are named by the provider stringsearch. With more than one search on a page, give each its own name. The clear button is named by the provider stringclear. - Focus
- The input is a tab stop, then the clear button while it shows. Focus turns the edge
--ring. Clearing keeps the focus in the input. - Known limits
- Inside a dialog, the dialog's own Escape handling runs first and closes it.
- A results count is the app's to show; put it in a
role="status"element so it is announced.
Keyboard
| Key | Behaviour |
|---|---|
| Enter | Runs the search: calls onSearch with the text. It never submits a form round the field. |
| Escape | Empties the field and keeps the focus in it; on an empty field, leaves the field. |
| Tab | Moves from the input to the clear button while it shows. |
API
SearchField
| Prop | Type | Default | Description |
|---|---|---|---|
keyFilter | InputKeyFilter | Lets only some characters in: int, num, money, hex, alpha, alphanum or a regular expression. Typed and
pasted text it refuses is not inserted. | |
loading | boolean | false | Shows a spinner at the end while results load, and marks the field busy. |
onSearch | ((value: string) => void) | Called with the text when the search is submitted with Enter. | |
size | "default" | "sm" | "lg" | The field's size: 28, 35 or 42 px tall. Defaults to the provider's controlSize. | |
variant | "default" | "filled" | filled draws the grey --field-filled fill. Defaults to the provider's fieldVariant. |
Every part takes className, merged with its defaults by cn(), and ref, which reaches the element it renders.
Data attributes: data-slot="search-field" (SearchField), and data-disabled.
Provider strings: search (BooleanUIProvider's strings).
Theming
The field is InputGroup's: --field, --control, --ring, --invalid and --field-disabled. The search icon and the clear ร are --control-hover; the spinner is --muted-foreground.
The tokens its classes read. Change their values in your theme, and it follows.
| Token | Used for |
|---|---|
--muted-foreground | text |