Spotlight Global Search
Site-wide search launched from a header button or the / key: the button morphs into a centred spotlight panel over a blurred backdrop, with grouped results and full keyboard control.
About This Component
A spotlight search for a whole site or app. The header search button morphs into the panel: the panel starts as a copy of the button's measured box and tweens width, height and position to its own, so the two read as one object rather than a button that fades out and a dialog that fades in. Behind it the page goes under a blurred backdrop.
Read the full effect overview
Inside, results are grouped under Pages, Docs and People with an icon per row and a live count on every heading, the matched run of characters is marked in each label, and a group with nothing left under it drops out. An empty field shows recent searches instead of a blank panel. Arrow keys move the selection with a rail sliding down the active row, Enter flashes it in the variant's accent, Escape closes and hands focus back to the button.
One HTML file, one stylesheet, one script, vanilla JavaScript and core GSAP, no framework and no plugins. Ships in three looks, vault, glass and brutal, switched with one data-variant attribute.
What's Included
- Launcher button morphs into the panel with a measured box tween, never a scale
- Blurred backdrop that fades with the morph
- Results grouped under Pages, Docs and People with a live count per heading
- Matched characters wrapped in mark, and empty groups hide themselves
- Recent searches when the field is empty
- Arrow keys wrap through the visible rows, Enter flashes the selection, Escape closes
- Opens on / anywhere on the page, and on Cmd+K or Ctrl+K
- combobox / listbox / option roles with aria-activedescendant
- Three shipped variants: vault, glass, brutal
Perfect For
- Global search in dashboards, admin portals and SaaS apps
- Docs sites that need one field across pages, guides and reference
- Directories where people and content share a result list
- Any header search box that currently opens a separate results page
How It Works
The morph
Opening measures the launcher and the panel with getBoundingClientRect, then tweens the panel from the launcher's width, height and centre offset to its own. Width and height are tweened rather than scaled, so the text inside is never squashed, and both are cleared when the tween lands so the panel goes back to being laid out by CSS. The panel carries no min-height, because a floor would stop the box shrinking back to the button on close.
Grouping and marking
Each keystroke walks the rows, tests the query against the row's data-label and toggles the hidden attribute, which the stylesheet restates as display:none because the row rule sets display:flex. Every group then counts what survived, writes the number into its heading badge and hides itself when the count is zero. Labels are re-rendered from data-label with the matched run wrapped in a mark element, so repeated filtering never compounds earlier markup.
Keyboard model
Focus never leaves the input. Arrow keys move an index over the visible rows only, wrapping at both ends, and the input carries aria-activedescendant so assistive technology follows the same row the rail is under. A document-level listener opens the panel on the / key when nothing is being typed into and on Cmd+K or Ctrl+K anywhere, and Escape closes the panel and returns focus to the launcher.