# Command Palette

> Build a keyboard-driven command palette (Cmd/Ctrl+K) with fuzzy filtering, arrow-key navigation, a focus trap, and accessible combobox/listbox semantics.

- Difficulty: intermediate
- Tags: useState, useEffect, useRef, keyboard, accessibility, ARIA
- Estimated time: 60 minutes
- Tests: 23
- Requires subscription: yes

## Overview

Build a keyboard-driven command palette (Cmd/Ctrl+K) with fuzzy filtering, roving highlight navigation, a focus trap, and proper combobox/listbox ARIA semantics.


## Requirements

- The palette is hidden on first render.
- Clicking the trigger (`data-testid="open-palette"`) opens the palette.
- Pressing Cmd/Ctrl+K opens the palette, and pressing it again closes it.
- Pressing Escape closes the palette.
- Clicking the backdrop (`data-testid="backdrop"`) closes the palette, while clicking inside the panel keeps it open.
- When the palette opens, the search input (`data-testid="command-input"`) receives focus and is empty.
- The palette lists every command.
- Typing filters the commands by fuzzy match: every character of the query must appear in the label in order, ignoring case.
- When nothing matches, show `data-testid="no-results"` with the text `No results for "<query>"`.
- Clearing the query restores the full list.
- The first command is highlighted when the palette opens and whenever the query changes.
- ArrowDown moves the highlight to the next command, wrapping from the last one to the first.
- ArrowUp moves the highlight to the previous command, wrapping from the first one to the last.
- Enter executes the highlighted command and closes the palette.
- Clicking a command executes it and closes the palette.
- Executing a command sets the `Last action` text (`data-testid="last-action"`) to that command's label.
- While the palette is open, focus stays inside it when pressing Tab.


## Notes

- `App.tsx` already renders the shell (`CommandTrigger`, `Last action`, backdrop, and panel) and uses the read-only components. Add the palette logic there — `data.ts` and `Components.tsx` are read-only.
- `CommandTrigger` takes `onClick`; `CommandInput` takes `value`, `onChange`, `onKeyDown`, and optional `inputRef`; `CommandList` takes `commands`, `activeIndex`, `query`, and `onSelect`; `CommandFooter` takes no props.
- Keep the `data-testid`s that live in `App.tsx`: `last-action` and `backdrop`.
- Open/close from a single `window` `keydown` listener (Cmd/Ctrl+K and Escape) and call `preventDefault()`.
- Filter by subsequence, not `includes` (`"gch"` → "Go to Challenges"). Reset `activeIndex` when the query changes and wrap with `% results.length`.
- Focus the input on open and keep focus trapped inside the panel (listen for `focusin`).


## Tests

01. keeps the palette hidden on first render
02. opens the palette when the trigger is clicked
03. opens the palette with Cmd/Ctrl+K
04. focuses the search input when opened
05. closes the palette when the shortcut is pressed again
06. closes the palette on Escape
07. closes the palette when the backdrop is clicked
08. keeps the palette open when clicking inside the panel
09. renders every command
10. filters commands as the user types
11. matches non-contiguous characters
12. is case-insensitive
13. shows a no-results message when nothing matches
14. restores the full list when the query is cleared
15. clears the query when the palette is reopened
16. highlights the first command by default
17. moves the highlight down with ArrowDown
18. wraps from the first to the last with ArrowUp
19. wraps from the last to the first with ArrowDown
20. resets the highlight when the query changes
21. executes the highlighted command with Enter
22. executes a command when clicked
23. traps focus inside the palette on Tab


