Skip to content
WANCRAFT

D.136Navigation & Menus

Expanding Search

A search icon that springs open into a full field with recent searches, suggestions and a / shortcut.

NewReactMotionTailwindintermediate

Overview

Expanding Search sits in a header as a single icon button and morphs into a full search field with a spring width animation, focusing the input as it opens. The field is a role="search" landmark with a visually hidden label and an ARIA 1.2 combobox: a listbox of recent searches and filtered suggestions (matched text underlined) is navigated with the arrow keys through aria-activedescendant, so focus never leaves the input. A single-key shortcut ("/" by default) opens it from anywhere unless the user is typing in another field; Escape collapses it and returns focus to the icon; a clear button empties the query; and the field folds back to the icon when focus leaves it empty. Submitting or picking a suggestion calls onSearch and records it as a recent search.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/expanding-search.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/expanding-search.json

Method 02 · Copy and paste

  1. 1. Install the dependencies below.
  2. 2. Copy the source from the Code tab into components/ui/expanding-search.tsx.
  3. 3. Import it and adjust props.
npm install motion
Dependencies
motion
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { ExpandingSearch } from "@/components/ui/expanding-search";

export function ExpandingSearchExample() {
  const suggestions = ["Magnetic button", "Command menu", "Date picker", "Toast stack", "Tilt card"];

  return (
    <header className="flex h-14 items-center justify-end px-4">
      <ExpandingSearch
        suggestions={suggestions}
        defaultRecent={["Combobox"]}
        onSearch={(query) => console.log(query)}
      />
    </header>
  );
}

03Props

PropTypeDefaultDescription
onSearch(query: string) => void—Called with the trimmed query on submit or when a suggestion is picked.
onQueryChange(query: string) => void—Called on every keystroke.
suggestionsstring[][]Suggestions, filtered by case-insensitive substring.
defaultRecentstring[][]Initial recent searches, most recent first.
showRecentbooleantrueShow and record recent searches.
maxRecentnumber5How many recent searches to keep.
onRecentChange(recent: string[]) => void—Called when the recent list changes (e.g. to persist it).
labelstring"Search"Visually hidden field label; also names the collapsed icon button.
placeholderstring"Search components…"Input placeholder.
shortcutstring"/"Single key that opens the search, ignored while typing in other fields. Empty string disables.
expandedWidthnumber320Open width in px (capped by the parent).
spring{ stiffness, damping, mass }{ 420, 34, 0.8 }Spring for the width morph.
collapseOnBlurbooleantrueFold back to the icon when focus leaves an empty field.
defaultOpenbooleanfalseStart expanded without stealing focus.
accentstring"#FF7448"Focus ring, caret and highlight colour.
...propsFormHTMLAttributes—Forwarded to the <form role="search">.

04Features

  • 01Icon button that springs open to a full field (motion width spring) and focuses the input
  • 02Recent searches and filtered suggestions with the matched text underlined
  • 03Configurable single-key shortcut, ignored inside inputs, textareas, selects and contenteditable
  • 04Escape collapses and returns focus; clear button; collapses on blur when empty
  • 05Submitting or choosing a suggestion calls onSearch and records it as recent (deduped, capped)
  • 06Listbox scrolls with scrollTop only, never moving the page
motion · springmotion · layoutinput · clickinput · keyboardinput · focus

05Accessibility

  • The form is a role="search" landmark; the input has a visually hidden <label> and the collapsed state is a labelled icon button with aria-keyshortcuts.
  • The input is an ARIA 1.2 combobox (aria-expanded, aria-controls, aria-autocomplete="list", aria-activedescendant); suggestions are options in labelled groups.
  • ArrowUp/ArrowDown move through suggestions, Enter searches the highlighted one (or the typed query), Shift+Delete forgets a highlighted recent search, Escape collapses and moves focus back to the icon.
  • Suggestion counts and searches are announced in a polite live region; the clear button has an accessible name and a visible focus ring.
  • prefers-reduced-motion opens and closes instantly without the spring.

06Limitations

  • Suggestions are filtered by simple substring matching; for remote search, update the suggestions prop from onQueryChange.
  • The "Clear recent" link is a pointer shortcut outside the tab order; keyboard users remove recents one at a time with Shift+Delete.