Source Code

<div class="demo-page">
  <div class="demo-hint">
    <p>Press <kbd>Alt</kbd> + <kbd>Tab</kbd> (or click below) to open the switcher, then <kbd>Tab</kbd> / arrow keys to cycle and <kbd>Enter</kbd> to select.</p>
    <button class="open-btn" id="open-btn">Open app switcher</button>
  </div>
  <p class="active-app">Active app: <strong id="active-app-name">Dashboard</strong></p>
</div>

<div class="switcher-overlay" id="switcher" hidden>
  <div class="switcher-panel">
    <div class="switcher-grid" id="switcher-grid"></div>
    <p class="switcher-hint">Tab to cycle &middot; Enter to open &middot; Esc to cancel</p>
  </div>
</div>

App Switcher Overlay — Free HTML CSS JS Snippet

App Switcher Overlay · Navigation · Plain HTML, CSS & JS · Live preview

What's included

Features

Genuine hold-to-cycle interaction: keydown advances focus while Alt+Tab is held, keyup on Alt triggers selection
Full two-dimensional arrow-key navigation (left/right by one, up/down by a full row) over a flat data array
Index wrapping via modulo arithmetic so navigation past either end of the list cycles around cleanly
Opens pre-focused on the next item after the currently active app, not the current app itself
Mouse hover and keyboard navigation share one setFocus() function, keeping visual state always consistent
Click and Enter/Alt-release selection paths share one selectApp() function
Escape key and backdrop click both close the overlay without changing the active app
Self-contained overlay with a blurred backdrop — no dependency on a specific app framework or router

About this UI Snippet

App Switcher Overlay — Hold-to-Cycle Key Handling and 2D Arrow-Key Grid Navigation

Screenshot of the App Switcher Overlay snippet rendered live

Desktop operating systems and many productivity suites share a familiar interaction: hold a modifier key, tap Tab repeatedly to cycle through open apps or recent items in a floating grid, and release the modifier to jump to whichever one is highlighted. This snippet implements that exact interaction model as a self-contained overlay — a grid of app cards, full keyboard cycling in two dimensions, and a genuine hold-and-release selection flow — for use inside a web app's own command surface rather than the OS-level switcher.

Hold-to-cycle via keydown and keyup working together

The core interaction spans two separate event listeners cooperating on shared state. A keydown listener detects Alt held together with Tab: on the first press it opens the overlay via openSwitcher(), and on every subsequent press while still open it advances focusedIndex by one (or backward with Shift) via setFocus() — exactly mirroring how holding Alt and tapping Tab repeatedly cycles further through a real OS switcher without closing it. A separate keyup listener watches specifically for the Alt key being released, and when it is, calls selectApp(focusedIndex) immediately — so releasing the modifier key is itself the selection gesture, not a separate click or Enter press, matching the muscle memory of the real OS pattern.

Two-dimensional arrow-key navigation over a 1D data array

The apps are stored as a flat array, but rendered into a four-column CSS grid, so up/down arrow navigation needs to move by a full row rather than by one array index. ArrowDown/ArrowUp add or subtract 4 (the fixed column count) from focusedIndex inside setFocus(), while ArrowLeft/ArrowRight and Tab/Shift+Tab move by exactly one. setFocus() wraps the resulting index with (i + APPS.length) % APPS.length, so navigating past either end of the flat array wraps around to the opposite end cleanly, regardless of whether the movement came from a row-jump or a single-step arrow key.

Opening pre-focused on the next item, not the current app

When the switcher opens, openSwitcher() deliberately does not focus the currently active app first — it looks up the active app's index and immediately focuses the *next* one ((startIdx + 1) % APPS.length). This mirrors real app switchers, where the very first Tab-hold already lands you on the second-most-recent item rather than re-highlighting the app you're already in, since selecting the app you're already using would be a no-op.

Mouse and keyboard sharing one focus model

Every .switcher-card also has a mouseenter listener wired to the same setFocus(i) function the keyboard uses, and a click listener wired to the same selectApp(i) function that Enter and Alt-release trigger. Because both input methods drive identical shared functions rather than separate parallel logic, the visual "focused" state (a highlighted border and background) and the selection behavior stay perfectly consistent regardless of whether the user is navigating with a mouse, a trackpad, or purely the keyboard.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly how the keydown and keyup listeners cooperate to implement the hold-to-cycle, release-to-select interaction, and why Up/Down arrow navigation adds or subtracts the fixed column count instead of using a different calculation. It's also a good candidate for extension — ask it to add a search/filter text input at the top of the panel that narrows the grid as the user types while still supporting arrow-key navigation over the filtered results, recently-used ordering so the grid re-sorts based on selection history, or a middle-click / dedicated close button per card for closing an app directly from the switcher instead of only selecting it.

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 an app-switcher overlay in plain HTML, CSS, and JavaScript — no libraries — that replicates the OS-style hold-Alt-Tab-to-cycle, release-to-select interaction pattern.

Requirements:
- A grid of app cards (icon plus name) that opens as a centered modal overlay with a dimmed, blurred backdrop, triggered either by a visible button or by pressing Tab while the Alt key is held down.
- While the overlay is open and Alt continues to be held, each additional Tab press (Shift+Tab for reverse) must advance a focused-card index by one, visually highlighting the newly focused card — this needs a keydown listener that does not close the overlay on repeated presses.
- Releasing the Alt key while the overlay is open must immediately select whichever card is currently focused and close the overlay — implemented via a separate keyup listener specifically watching for the modifier key going up, not a keydown or click.
- Also support Left/Right arrow keys moving focus by one card, and Up/Down arrow keys moving focus by a full grid row (i.e. by however many columns the grid has), with focus wrapping around from the last card back to the first and vice versa in every direction.
- Support Enter as an alternate way to select the currently focused card without requiring the modifier key to be released, Escape to close the overlay without changing selection, and clicking a card directly (or hovering it to move keyboard focus there) as equivalent alternate interactions — all selection paths should call one shared selection function, and all focus-change paths should call one shared focus function, so mouse and keyboard stay consistent.
- When the overlay opens, focus should land on the app immediately after the currently active one in the list, not on the currently active app itself.

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
    Open the switcherClick "Open app switcher" or hold Alt and press Tab. openSwitcher() shows the overlay and focuses the item just after the currently active app.
  2. 2
    Keep holding Alt and tap Tab to cycleEach additional Tab press while Alt is held advances focusedIndex by one via setFocus(), exactly like a real OS app switcher — Shift+Tab moves backward.
  3. 3
    Or navigate with arrow keysLeft/Right move one card at a time; Up/Down jump by a full row (4 cards) since the grid is two-dimensional but the underlying data is a flat array.
  4. 4
    Release Alt, or press Enter, to selectReleasing the Alt key (detected via a keyup listener) immediately selects whichever card is focused, matching real hold-and-release switcher behavior. Enter does the same without requiring a held modifier.
  5. 5
    Click a card directly, or press Escape to cancelClicking any card selects it immediately regardless of keyboard focus. Escape, or clicking the dimmed backdrop, closes the overlay without changing the active app.
  6. 6
    Edit the apps listUpdate the APPS array with your own name, color, and glyph (a short initials string) for each entry — buildGrid() regenerates the full card grid from whatever entries exist.

Real-world uses

Common Use Cases

In-app workspace or module switcher
Give a multi-module product (dashboard, inbox, calendar, files) a fast, keyboard-first way to jump between sections, similar to the command palette but optimized for visual, icon-based recent-item switching instead of text search.
Recent documents or recent workspaces overlay
Reuse the same hold-to-cycle pattern for a "recent files" or "recent projects" switcher, swapping app icons for document thumbnails or project avatars.
Multi-account or multi-tenant switcher
Let users holding a shortcut cycle between connected accounts, organizations, or tenants in a SaaS product without leaving the keyboard.
Reference for hold-and-release keyboard interaction patterns
A clear example of coordinating keydown and keyup listeners to implement a modifier-key-held interaction, useful for any UI that needs "hold to preview, release to commit" behavior.
Power-user productivity tool onboarding
Demonstrating this pattern in a product tour helps power users discover a faster way to move around than clicking through a sidebar nav.
Related: Voice Command Navigation Menu
See the Voice Command Navigation Menu for a related navigation pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

A keydown listener opens the switcher and advances the focused index on repeated Alt+Tab presses while the key combination is held. A separate keyup listener specifically watches for the Alt key being released and, if the switcher is open, immediately calls selectApp() on whatever card is currently focused — so releasing the modifier key is the selection trigger, not a separate action.

The apps are stored as a flat one-dimensional array but rendered into a 4-column CSS grid. Moving up or down visually means jumping a full row, so ArrowUp and ArrowDown add or subtract 4 (the fixed column count) from the focused index rather than 1, which is what Left/Right and Tab use.

setFocus() wraps the resulting index using (i + APPS.length) % APPS.length, so moving past the last item cycles back to the first, and moving before the first item cycles to the last, regardless of whether the navigation came from an arrow key or a Tab press.

openSwitcher() intentionally focuses the item immediately after the currently active app's index, because selecting the app you are already in would be a no-op — this mirrors how real OS-level app switchers land on the next most relevant item rather than re-highlighting your current app.

Yes. Pressing Enter while the switcher is open calls the exact same selectApp() function that releasing Alt triggers, and clicking a card directly also calls it — the modifier-hold pattern is one of three equivalent ways to select an item, not the only one.

Yes. Use the JSX, Vue, Angular, or Tailwind export buttons on this page. In React, keep focusedIndex and isOpen in state, and wire the same keydown/keyup logic to window listeners inside a useEffect that adds and cleans up the listeners.