Expanding search

An icon that morphs into a search field with results beneath it.

searchfieldmotion
Preview
Expanding search preview

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
Library

Keyboard interactions

KeysAction
Enter / SpaceOn the icon button, expands the field.
ArrowDown / ArrowUpMoves through results, wrapping.
EnterChooses the active result.
EscapeFolds 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.