KQ-1287 · SEARCH/EXPAND

Search Expand

An app header whose search icon grows into a field on the glide spring while the bar gives way to it frame by frame: the nav items nearest the field squeeze to their icons, then tuck into a More button that grows in with the first and squeezes on flick and snap as each lands, and the brand name steps aside before the last item goes — one motion value drives it all, and both rest states are rounded to whole items. The results panel then grows down out of the field itself on glide, its shell easing from a pill to a card with the matches rising in a cascade and a highlight that moves on snap, and Escape, a choice or blur unwinds it in reverse: the panel retracts on the exit ease before the field narrows and the items come back. The field is a combobox that `/` opens from anywhere, arrows move through results and Enter chooses; More is a menu button whose menu holds whatever is tucked.

KQ-1287 · SEARCH/EXPAND
Fernworks

Projects

4 projects

  • Basin Road surveyActive
  • Coldbrook wetlandReview
  • Waylight orchard trialDraft
  • Gauge river flowActive

search closed · 6 of 6 in the bar

Install

One command — the source lands in your repo. Or copy it from the Code tab.

$ pnpm dlx shadcn@latest add @kinetiq/search-expand

Props

nav
SearchExpandItem[]
default defaultSearchNav
The bar's navigation in order (id, label, icon). The items nearest the search give way first.
value / defaultValue
string
default the first item's id
The current page. It carries the wash pill, which moves on snap; tucked, it hands over to a dot on More.
onValueChange
(id: string) => void
default —
Fires from the press or the More menu choice that asked for a page.
index
SearchExpandEntry[]
default defaultSearchIndex
What the search searches: id, title, group, meta, icon, keywords. Results are grouped in the order groups first appear here.
recent
string[]
default defaultSearchRecent
Searches offered while the field is empty; choosing one fills the field.
onSelect
(entry: SearchExpandEntry) => void
default —
A result was chosen by a press or Enter. The search closes after it.
onQueryChange
(query: string) => void
default —
Every edit of the field, with its text.
open / defaultOpen
boolean
default false
Whether the search is open. Controlled, every request goes through onOpenChange and the bar follows the host.
onOpenChange
(open: boolean) => void
default —
Fires from the press, key or blur that asked to open or close it.
onTuckChange
(ids: string[]) => void
default —
The ids tucked into More once the bar settles, whenever that set changes.
width
number
default 320
How wide the open field grows, in px. A wider field squeezes and tucks more of the nav; it never takes more than the bar can spare.
results
number
default 5
How many results the panel lists at most.
tuck
number
default 0.5
How much of the nav squeezes to its icons before anything tucks into More, 0 to 1: 0 tucks items whole, 1 squeezes every one first.
brand
React.ReactNode
default a Fernworks mark
The brand mark, about 24px, always shown.
brandName
string
default "Fernworks"
The product name beside the mark; it steps aside just before the last nav item tucks.
actions
React.ReactNode
default —
The account slot at the bar's far end: an avatar, a button.
children
React.ReactNode
default —
The page under the bar. The results panel and the More menu float over it and never reach past its bottom.
placeholder
string
default "Search"
The field's placeholder, and the search button's accessible name.
label
string
default "Primary"
The navigation's accessible name.
moreLabel
string
default "More"
The More button's name; its accessible name adds the count and whether the current page is inside.
emptyLabel
(query: string) => string
default (q) => `No matches for “q”`
The row shown when nothing matches.
shortcut
string | null
default "/"
The key that opens the search from anywhere on the page that is not typing; null for none.
maxPanelHeight
number
default 320
The panel's tallest, in px, when nothing sits under the bar to bound it; past it the panel scrolls inside itself.
size
"sm" | "md" | "lg"
default "md"
Bar height 40, 48 or 56 px; the field and the items scale with it.
accent
string
default "var(--accent-bright)"
The current-page pill, the result highlight, the matched letters and More's dot. Any CSS colour.
sound
boolean
default false
A swish as the visitor opens or closes the field, and clicks for choices, for each item landing in More and, softly, for arrow keys. Nothing plays unless the visitor did something.
disabled
boolean
default false
Dims the bar and disables the search, the nav and the shortcut.
className
string
default —
Merged onto the root (a container-query box holding the bar and the page).