Expanding search
An icon that morphs into a search field with results beneath it.
searchfieldmotion
Preview
Expanding search preview
Live · keyboard ready
When to use
- Header or toolbar search where a full field would crowd the layout.
- Quick navigation across local items with grouped results and recent suggestions.
When not to use
- Use search-field to filter a visible list in place.
- Use combobox to set a form value.
- Use command-palette for searching commands across the app.
shadcn CLI
Also installs (1)
- manicat-foundation
Manual install(+)
registry.jsonjson
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "expanding-search",
"type": "registry:ui",
"title": "Expanding search",
"description": "An icon that morphs into a search field with results beneath it.",
"categories": [
"inputs"
],
"dependencies": [
"lucide-react",
"motion"
],
"registryDependencies": [
"https://manicat.viraza.net/r/manicat-foundation.json"
],
"files": [
{
"path": "registry/components/expanding-search/expanding-search.tsx",
"type": "registry:component",
"target": "@components/manicat/expanding-search/expanding-search.tsx"
},
{
"path": "registry/components/expanding-search/expanding-search.module.css",
"type": "registry:component",
"target": "@components/manicat/expanding-search/expanding-search.module.css"
}
],
"docs": "Docs and live preview: https://uiarc.dev/components/expanding-search",
"meta": {
"tier": "free",
"kind": "component",
"docs": "https://manicat.viraza.net/components/expanding-search",
"markdown": "https://uiarc.dev/components/expanding-search/markdown",
"tags": [
"search",
"field",
"motion"
]
}
}Usage
Install the component, then copy this into your app. Change the content and the props to fit your screen.
example.tsxtsx
"use client";
import { ExpandingSearch } from "@/components/manicat/expanding-search/expanding-search";
const searchItems = [
{ id: "button", title: "Button", group: "Actions", meta: "component", keywords: ["cta", "action"] },
{ id: "dialog", title: "Dialog", group: "Disclosure", meta: "component" },
{ id: "line-chart", title: "Line chart", group: "Data", meta: "component", keywords: ["graph", "series"] },
{ id: "hero-section", title: "Hero section", group: "Blocks", meta: "block" },
];
export function ExpandingSearchExample() {
return (
<div className="flex min-h-16 items-center justify-center">
<ExpandingSearch label="Search the library" placeholder="Search components" items={searchItems} />
</div>
);
}
Examples
Real screens rather than isolated props. Switch to Code to copy a whole example into your project.
Toolbar search A search that lives as an icon in a toolbar and grows into a field with grouped results.
Toolbar search
A search that lives as an icon in a toolbar and grows into a field with grouped results. preview
Live · keyboard ready
Library
toolbar-search-a-search-that-lives-as-an-icon-in-a-toolbar-and-grows-into-a-field-with-grouped-results-.tsxtsx
"use client";
import { ExpandingSearch } from "@/components/manicat/expanding-search/expanding-search";
const components = [
{ id: "button", title: "Button", group: "Actions", meta: "component", keywords: ["cta", "action"] },
{ id: "dialog", title: "Dialog", group: "Disclosure", meta: "component" },
{ id: "line-chart", title: "Line chart", group: "Data", meta: "component", keywords: ["graph", "series"] },
{ id: "hero-section", title: "Hero section", group: "Blocks", meta: "block" },
];
export function ExpandingSearchToolbarExample() {
return (
<div className="flex w-full items-center justify-between gap-3 rounded-[var(--radius-panel)] border border-(color:--border) bg-(--surface) p-3">
<span className="px-2 text-sm font-medium text-(color:--foreground)">Library</span>
<ExpandingSearch label="Search the library" placeholder="Search components" items={components} />
</div>
);
}
Keyboard interactions
| Keys | Action |
|---|---|
| Enter / Space | On the icon button, expands the field. |
| ArrowDown / ArrowUp | Moves through results, wrapping. |
| Enter | Chooses the active result. |
| Escape | Folds the field back into the icon. |
Accessibility
- The field is role="combobox" with aria-expanded, aria-controls, aria-autocomplete="list", and aria-activedescendant.
- Results are a role="listbox" of role="option" items inside labelled role="group" sections.
- Result counts and empty states are announced through a polite live region; the collapsed field is inert.
Motion
- The button morphs into the field on a spring, results unfold beneath with a panel that tracks the field width, and a highlight travels between options.
- Reduced motion swaps states with fades and no width or position travel.
Responsive behavior
- The field grows to the smaller of expandedWidth and its container, and a ResizeObserver follows container resizes.
- The results panel matches the field width and caps at min(22rem, 60vh), scrolling itself.
- The idle lane takes no pointer events, so it never blocks what sits under it on small screens.
Performance
- Ranking is memoized and runs over all items per query; pass pre-filtered or server results for large sets.
- Result rows use layout position animation, capped by maxResults at 6 by default.
Notes for AI
- Use in headers and toolbars where a full field would crowd the layout. Use search-field to filter a list in place and combobox to set a form value.
- Place it in the space it may grow into; it fills that space up to expandedWidth. Filtering is local, so pass pre-fetched items or update items as the query changes.
- Exported as both named and default.