shadcn.io is not affiliated with official shadcn/ui
Global Search Command Menu Block for React
A company intranet's command menu shown open: one input searches pages, docs, people and tags at once, each type is a group of three matches with a row that opens it in place, and the foot counts the results by type.
Installation
Overview
Search everything at once with this global search command menu for React with Base UI. It opens from the Intranet page of Yarlby Freight, a logistics company, whose header holds an outline search field with its ⌘ K cap, and the page itself is drawn under a soft scrim inside a hairline frame with a plus mark on each corner. The menu is the catalog Command: one input looks through pages, docs, people and tags together, matching a title, a page's path, a person's role or place, or a few hidden keywords, and the letters that match are marked with Search Highlight Text. Each type is a group with its count in the heading and its first three matches, and a group with more ends in a row such as "Show all 9 people" that opens it in place, keeps the query and the row you were on, and turns into "Show fewer people". People carry an initials Avatar, documents a file-type Badge, and the foot lists the keys and counts the results, "14 results in 4 types", which is also spoken a moment after typing stops. Choosing a result moves the drawn page's breadcrumb to it and says so in the status line. For preview the menu is drawn open in place when the page loads; the search field, or Ctrl or ⌘ K while focus is in the block, opens the real command dialog with the same rows and the same query. For intranets, help centres, admin consoles and any app with more than one kind of thing to find.
FAQ
Why is the menu already open, and what do I keep?
So you can see it without a click. On load the menu is the Command drawn inline over a picture of the intranet, in a plain shell that is not modal, so nothing is trapped, hidden or focused and the page around the preview keeps working. The search field opens the real command dialog, modal, with its focus trap, Escape and focus return, and the inline copy stays closed until a reload. Both show the same Palette component and share the block's state, so what you typed in the copy is in the real dialog. In your app keep the dialog and drop the copy: the "in-place" state, the scrim with its shell and the Escape and blur effects.
How do the rows and the match order work?
rowsFor(query, opened) is a plain function from the query and the groups you opened to the rows, so every library renders the same list. A type's matches are the hits whose title, path or role, or keywords hold the query, ignoring case and accents, with title matches first. A group shows three, and past three its last row opens it in place: the query stays, the row stays highlighted, and the polite status line inside the menu says "Showing all 9 people". Typing starts the groups over at three.
What does choosing a result do, and how do I wire it up?
Here it changes the last crumb of the drawn page and the status line says which page, document, person or tag was opened and that nothing loaded. In your app send the visitor to the result's address in pick, or navigate with your router; and replace the typed ALL list with your search endpoint's results, turning each hit into a row in rowsFor, with the counts from the response rather than from the list.
Is the count really spoken?
Yes, once typing pauses for about half a second, never on every key: a polite live region in the foot of the menu says "14 results in 4 types". It sits inside the menu on purpose, because a modal dialog hides everything around it from a screen reader. The visible count is hidden from assistive technology so it is not read twice.
Is it accessible from the keyboard?
Yes. Type to search, the arrow keys move through the rows, Enter chooses or opens a group, and Escape closes the menu and puts focus on the search field; Ctrl or ⌘ K opens the dialog while focus is inside the block, and only then, because the site's own search owns the key everywhere else. The rows are options of a cmdk listbox; the block turns off its own fuzzy filter and lists the rows itself. The real dialog is named by its title and described by its sentence.
Related
- 471Pro
Docs Search Command Menu
Search everythingA docs host's scoped search shown open under the header field: chips for All, Guides, Reference and Releases that carry how many pages match, and results with a path and one line of the page with the matched words marked.
- Pro
Basic Command Menu
Search everythingA team workspace's home page with its command menu shown open: eleven pages in three groups, a shortcut on the five most used, matches marked, an empty state that says what to try and a foot with the key hints and a spoken count.
- Pro
Navigation Command Menu
Pages and docsA bank back office's navigation menu shown open: 24 pages in three levels, each row the name over its whole path, a search that reads the path by the start of each word, and a tick on the page you are on.
- Pro
Recent Items Command Menu
Search everythingA project hub's command menu shown open on what you opened lately: Today, Yesterday and Earlier buckets before you type, recent matches first when you do, opening a row moves it to the top, and a last row clears the list.
- Pro
A workspace's search as a panel in the page: a field in the first band, results grouped as Pages, Documentation, Team and Settings with their counts, arrow keys that walk them, Enter that opens one locally, and an empty state that names the query.
- Pro
Command Palette FAQ
SearchA FAQ for a docs host with a working command palette: a search-field button with its shortcut opens a palette of questions by topic, and choosing one opens its row below.