Expanding search
FreeA search that expands from an icon into a results list.
pnpm dlx shadcn@latest add https://caliperui.site/r/expanding-search.jsonNothing chosen yet.
When to use #
Use it for
- Headers where space is limited.
- Searching a known list of items with groups.
Skip it when
- Complex queries; use a full search page.
Installation #
terminal
pnpm dlx shadcn@latest add https://caliperui.site/r/expanding-search.json
Adds its 2 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.
Usage #
This is the code behind the preview above.
example.tsx
import { useState } from "react" import { ExpandingSearch } from "@/components/ui/expanding-search" import { Search } from "@/components/ui/search" export function ProjectSearch() { const [picked, setPicked] = useState("Nothing chosen yet."); return ( <div className="grid w-full max-w-sm gap-3"> <ExpandingSearch label="Search projects" items={[ { id: "atlas", title: "Atlas platform", group: "Projects", meta: "Active" }, { id: "harbour", title: "Harbour redesign", group: "Projects", meta: "In review" }, { id: "notes", title: "Release notes", group: "Documents" }, ]} onSelect={(item) => setPicked(`Opened ${item.title}.`)} /> <p role="status" className="text-sm text-muted-foreground">{picked}</p> </div> ); }
API reference #
ExpandingSearch
component
PropTypeDefault
label *string—
items *readonly ExpandingSearchItem[]—
onSelect((item: ExpandingSearchItem) => void)—
placeholderstring"Search"
emptyLabelstring"No matches."
classNamestring—
shortcutstring—
surface"separate" | "connected""separate"
Accessibility #
It exposes combobox, listbox, group and option semantics, with aria-label, aria-expanded, aria-controls, aria-hidden, aria-autocomplete and aria-activedescendant kept in step with state.
KeyBehaviour
↓Moves to the next item or value.
↑Moves to the previous item or value.
EnterActivates the focused control.
EscapeCloses or cancels, then returns focus to where it came from.
Motion #
Transitions use the shared duration and easing tokens. Under prefers-reduced-motion those tokens collapse to instant and travel distances go to zero, so state changes still happen without movement.