Search
Where typing beats browsing: site and collection search on Base UI's Autocomplete, in a search landmark. Don't make search the only route into content that also needs browsing, and pick a Combobox for one value from a long known set.
import { Search } from '@fairgarden-private/design/components/Search'
label names both the field and the landmark; the placeholder ("Search the guide…") never replaces it. The field is the Input's box, with the ring on any focus: in the butted build it wraps the field and the submit together. Matches are marked by weight, not color, and an empty result is a plain-word status with a next step, never a red error. Show one docked bar at a time.
Kinds and states
Kinds and states
boxed (default) is the field with the butted submit: the icon-only 64 × 48 px cell at base, labelled from --md-n-above with submitLabelled. ruled sets the field between two --border-size-2 rules. trigger is the header's icon-only Button; wire it to open a Dialog sheet below --lg-n-above and a docked bar from it. docked puts the boxed field in the full-width top bar. Suggestions come in groups under a rule, with an optional type icon and an italic secondary name; status announces "12 results", and seeAllHref adds the standalone "See all results" link.
boxed
Type "egret", then press Enter.
ruled
trigger
docked
Scroll this frame: the docked bar holds the top edge, alone.
Herons stand still in the shallows and strike fast.
Egrets hunt the same water in brighter white.
Bitterns hide in the reeds and point their bills skyward.
'use client'
import * as React from 'react'
import { Search, type SearchSuggestionGroup } from '@fairgarden-private/design/components/Search'
import styles from './states.module.css'
const suggestions: SearchSuggestionGroup[] = [
{
label: 'Species',
items: [
{ value: 'Great Egret', secondaryName: 'Ardea alba', href: '#egret' },
{ value: 'Snowy Egret', secondaryName: 'Egretta thula', href: '#snowy' },
{ value: 'Reddish Egret', secondaryName: 'Egretta rufescens', href: '#reddish' },
],
},
{
label: 'Places',
items: [
{ value: 'Egret Point', icon: 'chevron_right', href: '#point' },
{ value: 'Egret Marsh Trail', icon: 'chevron_right', href: '#marsh' },
],
},
]
/**
* Boxed with the butted submit and grouped suggestions, rule-bounded, the
* header trigger, and the docked bar (inside a short scrolling frame).
*/
export function SearchKinds() {
const [submitted, setSubmitted] = React.useState('')
return (
<div className={styles.stack}>
<div>
<p className={styles.label}>boxed</p>
<Search
label="Search the guide"
placeholder="Search the guide…"
items={suggestions}
status="5 suggestions"
seeAllHref={(query) => `#results-${encodeURIComponent(query)}`}
submitLabelled
onSubmit={(query) => setSubmitted(query)}
/>
<p className={styles.label} aria-live="polite">
{submitted ? `Searched for "${submitted}"` : 'Type "egret", then press Enter.'}
</p>
</div>
<div>
<p className={styles.label}>ruled</p>
<Search kind="ruled" label="Filter events" placeholder="Filter events…" />
</div>
<div>
<p className={styles.label}>trigger</p>
<Search kind="trigger" label="Search" onClick={() => setSubmitted('the trigger')} />
</div>
<div>
<p className={styles.label}>docked</p>
<div className={styles.dockFrame}>
<Search kind="docked" label="Search the guide" items={suggestions} />
<p>Scroll this frame: the docked bar holds the top edge, alone.</p>
<p>Herons stand still in the shallows and strike fast.</p>
<p>Egrets hunt the same water in brighter white.</p>
<p>Bitterns hide in the reeds and point their bills skyward.</p>
</div>
</div>
</div>
)
}
Primary and secondary
Primary and secondary
Search's own parts take primary; the submit takes the scope's action scale, and the popup the white scope's defaults.
'use client'
import { Search } from '@fairgarden-private/design/components/Search'
import styles from './color.module.css'
const items = [{ value: 'Owls' }, { value: 'Otters' }, { value: 'Orchids' }]
/**
* `primary` recolors the field, magnifier and rows' ink; the submit keeps
* the scope's action scale, as every `solid` Button does.
*/
export function SearchColor() {
return (
<div className={styles.stack}>
<Search label="Search, primary plum" primary="plum" items={items} />
<Search kind="ruled" label="Filter, primary indigo" primary="indigo" items={items} />
</div>
)
}
On grounds
On paper and forest
The field keeps its open box on every ground; the rules are the ground's --primary12.
paper
forest
'use client'
import { PresetGround } from '@/components/PresetGround'
import { Search } from '@fairgarden-private/design/components/Search'
import styles from './grounds.module.css'
const items = [{ value: 'Moths' }, { value: 'Mosses' }, { value: 'Mushrooms' }]
const presets = ['paper', 'forest'] as const
/**
* On forest the submit stays amber with its edge equal to its fill, and
* the suggestions popup is the white scope, which follows the page mode.
*/
export function SearchGrounds() {
return (
<div className={styles.row}>
{presets.map((preset) => (
<PresetGround key={preset} preset={preset} className={styles.face}>
<p className={styles.name}>{preset}</p>
<Search label={`Search on ${preset}`} items={items} />
<Search kind="ruled" label={`Filter on ${preset}`} items={items} />
</PresetGround>
))}
</div>
)
}
API Reference
Search in a search landmark. Matches are marked by weight, never
color; an empty result is a plain-word status, never a red error (P10).
Show one docked bar at a time.
| Prop | Type | Description |
|---|---|---|
name | | The query’s name in a native submission. Default |
label | | The field’s label and the landmark’s name (“Search the guide”). A placeholder never replaces it. |
defaultValue | | The initial query. |
value | | The query, controlled. |
onValueChange | | |
onClick | | Opens the app’s search sheet (a Dialog below |
action | | A native form action, used when |
emptyText | | The empty status. Default “No results for ‘[query]'"; add a next step where you can. |
filter | | Base UI’s filter; |
hideSubmit | | Hides the butted submit (boxed and docked); Enter still submits. |
items | | The suggestions, or titled groups. Pass |
kind | |
|
labelVisible | | Shows |
loading | | Shows “Searching…” in the status row while suggestions load. |
onSubmit | | Called with the query on submit (Enter, or the submit cell); the page does not reload. |
placeholder | | Default “Search…", ending in the ellipsis character. |
primary | | Primary Radix scale: field, rules, rows and focus ring. Never defaulted [D133]. |
secondary | | Secondary Radix scale: accepted; the submit takes the scope’s action scale. |
seeAllHref | | The “See all results” row’s destination for a query. |
seeAllText | | The “See all” row’s words. Default “See all results for ‘[query]'". |
status | | Status text: “12 results”. Announced politely. |
submitLabel | | The submit cell’s accessible name (and label from |
submitLabelled | | Shows the submit’s label from |
submitOnItemClick | | Submits the chosen suggestion when a row is pressed. |
disabled | | |
className | | Class names for the landmark, added after the module’s own. |
render | | Base UI |
SearchProps
Props for Search. kind="trigger" takes only the trigger props; the
other kinds take the field props.
type SearchProps = (
| {
kind?: 'boxed' | 'ruled' | 'docked';
labelVisible?: boolean;
placeholder?: string;
value?: string;
defaultValue?: string;
onValueChange?: (value: string) => void;
onSubmit?: (query: string, event: React.FormEvent<HTMLFormElement>) => void;
action?: string;
name?: string;
items?: SearchItems;
filter?: __type | null;
status?: React.ReactNode;
loading?: boolean;
emptyText?: (query: string) => React.ReactNode;
seeAllHref?: (query: string) => string;
seeAllText?: (query: string) => React.ReactNode;
submitLabel?: string;
submitLabelled?: boolean;
hideSubmit?: boolean;
submitOnItemClick?: boolean;
disabled?: boolean;
}
| {
kind: 'trigger';
onClick?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
render?: ReactElement;
}
) & {
/**
* The field's label and the landmark's name ("Search the guide"). A
* placeholder never replaces it.
*/
label: string;
/** Class names for the landmark, added after the module's own. */
className?: string;
/** Primary Radix scale: field, rules, rows and focus ring. Never defaulted [D133]. */
primary?:
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'ruby'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null;
/** Secondary Radix scale: accepted; the submit takes the scope's action scale. */
secondary?:
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'ruby'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| 'amber'
| 'blue'
| 'cyan'
| 'grass'
| 'green'
| 'jade'
| 'lime'
| 'mint'
| 'orange'
| 'sky'
| 'teal'
| 'yellow'
| null;
}SearchSuggestion
One suggestion row.
type SearchSuggestion = {
/** The text the suggestion fills in (and submits). */
value: string;
/** The row's words. Default: `value`. */
label?: string;
/** An italic secondary name in --role-muted (a Latin name, a place). */
secondaryName?: string;
/** An optional type icon (inline tier). */
icon?:
| 'search'
| 'arrow_forward'
| 'arrow_upward'
| 'expand_more'
| 'close'
| 'remove'
| 'add'
| 'check'
| 'circle'
| 'chevron_right'
| 'chevron_left'
| 'menu'
| 'more_horiz'
| 'play_arrow'
| 'pause'
| 'download'
| 'zoom_in'
| 'zoom_out'
| 'recenter'
| 'help'
| 'mail';
/** Makes the row a link to this page. */
href?: string;
disabled?: boolean;
}SearchSuggestionGroup
A titled group of suggestions; each group after the first sits under a rule.
type SearchSuggestionGroup = {
/** The group head, in `type-label` caps. */
label: string;
items: SearchSuggestion[];
}Specification: DESIGN-SYSTEM.md §9.10 (search), with the field box of §10.1 and the butted cell of §10.3.