Command palette
A palette that opens with ⌘K, and unmounts completely when it closes. The shortcut keeps working anyway, because it does not live in the palette.
Mounted
A provider, a trigger and the palette itself. The palette is the only thing that renders any UI, and it is gone from the tree whenever it is closed.
// -components/palette_demo.tsx
export const PaletteDemo = () => (
<GraftPalette.Provider>
<PaletteTrigger />
<PaletteLayer />
</GraftPalette.Provider>
);The graft
Open, closed, the query and what matches it — plain state, and the operations on it.
// -constants/graft_palette.ts
export const GraftPalette = createGraft({
name: "GraftPalette",
graft: useGraft,
inject: [useInjectShortcut],
});
function useGraft() {
const [isOpen, setIsOpen] = useState(false);
const [query, setQuery] = useState("");
const open = useCallback(() => {
setQuery("");
setIsOpen(true);
}, []);
const close = useCallback(() => setIsOpen(false), []);
const results = useMemo(() => {
const term = query.trim().toLowerCase();
if (!term) return COMMANDS;
return COMMANDS.filter((command) => command.toLowerCase().includes(term));
}, [query]);
return { isOpen, query, results, close, open, setQuery };
}The shortcut is injected
It takes the value of the graft as its argument, listens on the window, and toggles. Being injected, it is a sibling of children and belongs to the provider — so it is listening even when the palette is not there.
function useInjectShortcut({ close, isOpen, open }: { close: () => void; isOpen: boolean; open: () => void }) {
useEffect(() => {
const onKeyDown = (event: KeyboardEvent) => {
if (event.key !== "k" || !(event.metaKey || event.ctrlKey)) return;
event.preventDefault();
if (isOpen) return close();
open();
};
window.addEventListener("keydown", onKeyDown);
return () => window.removeEventListener("keydown", onKeyDown);
}, [close, isOpen, open]);
}Why it cannot live in the palette
Write the same listener inside the component and it can only run while that component is mounted. The palette is unmounted precisely when it is closed, which is the only moment the shortcut has anything to do — so it would open the palette only when the palette is already open.
// the same listener, written inside the palette
export const PaletteLayer = () => {
const { close, isOpen, open } = GraftPalette.use();
useShortcut(isOpen, open, close); // only runs while the palette is mounted
return (
<Layer open={isOpen} onClose={close}>
…
</Layer>
);
};Lifting it into whatever screen happens to be around fixes the timing and creates the other problem: from then on that screen cannot be removed, or reused elsewhere, without the shortcut silently going with it.