Source Code

<header class="fso-bar">
  <span class="fso-logo">◆ Acme Docs</span>
  <button type="button" class="fso-open" id="fsoOpen" aria-label="Open search">
    <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.3-4.3"/></svg>
    <span>Search</span><kbd>⌘K</kbd>
  </button>
</header>

<div class="fso-overlay" id="fsoOverlay" role="dialog" aria-modal="true" aria-label="Search" hidden>
  <div class="fso-panel">
    <div class="fso-field">
      <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.3-4.3"/></svg>
      <input type="text" id="fsoInput" placeholder="Search documentation…" autocomplete="off">
      <button type="button" class="fso-close" id="fsoClose" aria-label="Close">Esc</button>
    </div>
    <ul class="fso-results" id="fsoResults"></ul>
    <p class="fso-empty" id="fsoEmpty" hidden>No results for "<span id="fsoQ"></span>"</p>
  </div>
</div>

Fullscreen Search Overlay — ⌘K Search Modal JS

Fullscreen Search Overlay · Modals · Plain HTML, CSS & JS · Live preview

What's included

Features

⌘K / Ctrl+K toggle
A document keydown handler opens and closes the overlay on the platform shortcut.
Full keyboard model
Esc closes, ↑/↓ move the active row with wraparound, Enter opens it.
Active-row scroll-into-view
The highlighted result scrolls into view so it never disappears in a long list.
Mouse/keyboard in sync
Hovering a row activates it too, keeping both input modes consistent.
Live filtering
Results filter on every keystroke against title and category.
Safe match highlighting
Matched text is wrapped in <mark> after escaping, so data can't inject HTML.
Smooth open/close + focus
Backdrop fade and panel ease via an rAF-deferred class, with focus timed to the animation.
Accessible dialog
role="dialog" with aria-modal, plus an advertised ⌘K affordance.

About this UI Snippet

Fullscreen Search Overlay — ⌘K Modal Search with Keyboard Navigation

Screenshot of the Fullscreen Search Overlay snippet rendered live

The fullscreen search overlay — invoked with ⌘K (or Ctrl+K), dimming the page behind a centred search panel with live results — is the search pattern of modern docs and apps (Algolia DocSearch, Linear, GitHub). This snippet builds it in plain HTML, CSS, and vanilla JavaScript: a keyboard-first overlay with live filtering, match highlighting, full arrow-key navigation, and the open/close conventions users expect — no library.

Keyboard-first by design

The whole point of this pattern is that power users never touch the mouse. A document-level keydown handler opens the overlay on ⌘K / Ctrl+K (and toggles it closed on a second press), Escape closes it, ArrowUp/ArrowDown move the active result with wraparound, and Enter opens the highlighted one. The trigger button advertises the ⌘K shortcut in a <kbd> so the affordance is discoverable. Building the keyboard model first — not as an afterthought — is what separates a real command-style search from a styled input.

An active row that the keyboard drives

Results maintain an active index, and the matching row gets a highlight class as you arrow through, scrolling into view with scrollIntoView({ block: 'nearest' }) so the selection never disappears off-screen in a long list. Hovering a row also activates it, so mouse and keyboard stay in sync. Pressing Enter (or clicking) "opens" the active result — here logged to the console, where you'd navigate to its URL.

Live filtering with safe highlighting

Typing filters the list on every keystroke against both the title and category, and the matched substring in each title is wrapped in a <mark>. The highlighting escapes all text before inserting the mark, so result data can never inject HTML — the safe escape-then-wrap order. An empty state shows the exact query when nothing matches, which is friendlier than a blank panel.

Smooth open, focus, and dismiss

Opening reveals the overlay then adds a class on the next animation frame so the backdrop fades and the panel eases down into place (the rAF defer is what lets the entry transition run from its start state). Focus moves to the input after a short delay so the cursor lands as the panel settles, not mid-animation. Clicking the dimmed backdrop closes it, and closing reverses the transition before hiding and clearing the query. The dialog is marked role="dialog" with aria-modal for assistive tech.

Drop-in and adaptable

Results come from a DOCS array, so pointing it at your pages — or debouncing a call to a real search API — is straightforward. It's a complete, dependency-free reference for the ⌘K overlay search every documentation site and app eventually wants, covering the keyboard model, active-row management, and safe highlighted filtering. If you swap the static DOCS array for a network call, debounce the input handler (roughly 150–250ms) before firing the request — searching on every keystroke against a local array is free, but against an API it would fire a request per character and race results out of order.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You don't have to trace the metaKey/ctrlKey branching or the escape-then-mark highlighting by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why the open function defers adding the fso-show class to a requestAnimationFrame callback instead of adding it in the same call as removing the hidden attribute, and why the active index wraps with modulo arithmetic in the move function instead of clamping at the ends. The same assistant can help optimize it — ask whether filtering the static DOCS array on every keystroke would still be safe to do unthrottled against a real backend search API, or whether the results list should virtualize rendering once the dataset grows into the hundreds. It's also a good way to extend the overlay: have it add recent-search history shown when the input is empty, keyboard shortcuts to jump straight to a specific category, or fuzzy matching instead of plain substring search. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a ⌘K/Ctrl+K fullscreen search overlay in plain HTML, CSS, and JavaScript — no libraries, keyboard-first by design.

Requirements:
- A header button advertising a keyboard shortcut hint (a kbd element showing the platform shortcut) that opens a search overlay when clicked.
- A document-level keydown listener that detects Cmd+K on Mac and Ctrl+K on Windows/Linux (checking both metaKey and ctrlKey so one handler covers both platforms), calls preventDefault so the browser's own shortcut doesn't fire, and toggles the overlay open or closed depending on its current state.
- The overlay must start hidden via the HTML hidden attribute. Opening it must remove that attribute, then on the next animation frame (not the same tick) add a class that triggers the backdrop fade-in and the panel's ease-down-into-place transition — explain why deferring to a second frame is required for the transition to actually play instead of snapping instantly. Focus must move into the search input shortly after the panel becomes visible, not immediately on open.
- A results list filtered live on every keystroke against at least two fields per result (e.g. a title and a category), where the matching substring in the title is wrapped in a highlight element — but only after the full text has been HTML-escaped, so result data can never break out into real markup.
- Full keyboard navigation within the open overlay: Escape closes it; ArrowDown and ArrowUp move an "active" result index with wraparound (looping from the last result back to the first and vice versa) and scroll the newly active result into view without scrolling the whole page; Enter activates whichever result is currently active. Hovering a result with the mouse must also mark it active, so keyboard and mouse selection state can never disagree.
- Show a distinct empty-state message that echoes back the user's exact search query when no results match. Clicking the dimmed backdrop (but not the panel itself) must close the overlay.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA top bar renders with a Search button showing a ⌘K hint.
  2. 2
    Open with ⌘KPress ⌘K (or Ctrl+K), or click the button, to open the fullscreen search overlay focused on the input.
  3. 3
    Type to filterResults filter live against title and category, with matches highlighted.
  4. 4
    Navigate by keyboardUse ↑/↓ to move the active result (it scrolls into view) and Enter to open it.
  5. 5
    Close itPress Esc, click the backdrop, or press ⌘K again to close.
  6. 6
    Wire to your dataReplace the DOCS array (or debounce a search API) and navigate on Enter/click.

Real-world uses

Common Use Cases

Documentation search
The DocSearch-style ⌘K overlay for docs — pair with a FAQ search accordion for help content.
App command & navigation
Jump to pages or run actions, alongside a command palette for command-first UIs.
E-commerce site search
A focused product search overlay from any page header.
Dashboard global search
Find records across an admin app next to an expandable search for inline navbar search.
Knowledge bases & wikis
Fast keyboard search over articles.
Learning overlay search UX
A reference for the ⌘K keyboard model and highlighted filtering — compare with an autocomplete input.
Related: Newsletter Signup Popup Modal
See the Newsletter Signup Popup Modal for a related modals pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

A document-level keydown handler checks for (e.metaKey || e.ctrlKey) with key 'k' — metaKey is ⌘ on macOS and ctrlKey covers Ctrl+K on Windows/Linux. It calls preventDefault so the browser's own shortcuts don't fire, and toggles the overlay (open if closed, close if open). The trigger button shows a ⌘K kbd hint so the shortcut is discoverable.

An active index tracks the highlighted result. Arrow keys move it with wraparound (modulo the list length) and scroll it into view; hovering a row sets it active too, via CSS and the same highlight class. Enter or a click opens whichever row is active. Because both input modes write to the same active state, they never disagree.

Yes. Each result's text is HTML-escaped first, and only the matched substring is then wrapped in <mark>. Because escaping happens before any markup is added, result data containing <, >, or & renders as text and can't inject elements — the correct escape-then-wrap order when building highlighted HTML with innerHTML.

The overlay starts hidden. To animate it in, it's first un-hidden (display restored), then the fso-show class is added on the next animation frame. That gap lets the browser paint the start state (transparent, panel offset) before the transition target is applied, so the fade and panel ease actually animate instead of snapping. Adding the class in the same tick would skip the transition.

Hold open state and the query in component state; render results from a filtered list. Attach the ⌘K and Escape listeners in a useEffect (React), onMounted/onUnmounted (Vue), or HostListener (Angular), cleaning up on unmount. Track the active index in state for arrow navigation. The filtering and highlight logic is framework-agnostic — only the listeners and state move into the framework.