Command Palette
A Command Palette is an interface that allows users to quickly run commands or navigate to content within an application.
Example#
This example uses the Autocomplete
component from React Aria Components to filter a list of commands and display them in a Menu
. The TextField
is used to capture user input and filter the list of available commands.
import {Autocomplete, Button, Dialog, DialogTrigger, Input, Menu, MenuItem, Modal, ModalOverlay, TextField, useFilter} from 'react-aria-components';
import {useEffect, useMemo, useState} from 'react';
function CommandPaletteExample() {
let commands = [
{ id: 'new-file', label: 'Create new file…' },
{ id: 'new-folder', label: 'Create new folder…' },
{ id: 'assign', label: 'Assign to…' },
{ id: 'assign-me', label: 'Assign to me' },
{ id: 'status', label: 'Change status…' },
{ id: 'priority', label: 'Change priority…' },
{ id: 'label-add', label: 'Add label…' },
{ id: 'label-remove', label: 'Remove label…' }
];
let [isOpen, setOpen] = useState(false);
let { contains } = useFilter({ sensitivity: 'base' });
let isMac = useMemo(() => /Mac/.test(navigator.platform), []);
useEffect(() => {
const handleKeyDown = (e) => {
if (e.key === 'k' && (isMac ? e.metaKey : e.ctrlKey)) {
e.preventDefault();
setOpen((prev) => !prev);
} else if (e.key === 'Escape') {
e.preventDefault();
setOpen(false);
}
};
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
});
return (
<div className="bg-linear-to-r from-indigo-500 to-violet-500 p-4 sm:p-8 h-[340px] rounded-lg flex items-center justify-center">
<DialogTrigger isOpen={isOpen} onOpenChange={setOpen}>
<Button className="inline-flex items-center justify-center rounded-xl bg-black/20 bg-clip-padding border border-white/20 px-3 py-2 font-medium font-[inherit] text-sm sm:text-base text-white hover:bg-black/30 pressed:bg-black/40 transition-colors cursor-default outline-hidden focus-visible:ring-2 focus-visible:ring-white/75">
<span className="block sm:hidden">Tap to open</span>
<span className="hidden sm:block">
Type{' '}
<kbd className="px-2 py-1 m-1 text-xs font-semibold border border-gray-200 rounded-lg">
{isMac ? '⌘' : 'Ctrl'}
</kbd>{' '}
+{' '}
<kbd className="px-2 py-1 m-1 text-xs font-semibold border border-gray-200 rounded-lg">
K
</kbd>{' '}
or press here to open
</span>
</Button>
<ModalOverlay
isDismissable
className={({ isEntering, isExiting }) => `
fixed inset-0 z-10 overflow-y-auto bg-black/25 flex min-h-full items-start sm:items-center justify-center p-4 text-center
`}
>
<Modal
className={({ isEntering, isExiting }) => `
`}
>
<Dialog className="outline-hidden relative">
<div className="flex flex-col gap-1 w-[95vw] sm:w-[500px] max-w-full rounded-xl bg-white shadow-lg p-2">
<Autocomplete filter={contains}>
<TextField
aria-label="Search commands"
className="flex flex-col px-3 py-2 rounded-md outline-none placeholder-white/70"
>
<Input
autoFocus
placeholder="Search commands…"
className="border-none py-2 px-3 leading-5 text-gray-900 bg-transparent outline-hidden text-base focus-visible:ring-2 focus-visible:ring-violet-500 rounded-lg"
/>
</TextField>
<Menu
items={commands}
className="mt-2 p-1 max-h-44 overflow-auto"
>
{({ label }) => <CommandItem>{label}</CommandItem>}
</Menu>
</Autocomplete>
</div>
</Dialog>
</Modal>
</ModalOverlay>
</DialogTrigger>
</div>
);
}
function CommandItem(props) {
return (
<MenuItem
{...props}
className="group flex w-full items-center rounded-md px-3 py-2 box-border outline-none cursor-default text-gray-900 hover:bg-violet-100 pressed:bg-violet-200 focus:bg-violet-500 focus:text-white"
/>
);
}
import {
Autocomplete,
Button,
Dialog,
DialogTrigger,
Input,
Menu,
MenuItem,
Modal,
ModalOverlay,
TextField,
useFilter
} from 'react-aria-components';
import {useEffect, useMemo, useState} from 'react';
function CommandPaletteExample() {
let commands = [
{ id: 'new-file', label: 'Create new file…' },
{ id: 'new-folder', label: 'Create new folder…' },
{ id: 'assign', label: 'Assign to…' },
{ id: 'assign-me', label: 'Assign to me' },
{ id: 'status', label: 'Change status…' },
{ id: 'priority', label: 'Change priority…' },
{ id: 'label-add', label: 'Add label…' },
{ id: 'label-remove', label: 'Remove label…' }
];
let [isOpen, setOpen] = useState(false);
let { contains } = useFilter({ sensitivity: 'base' });
let isMac = useMemo(
() => /Mac/.test(navigator.platform),
[]
);
useEffect(() => {
const handleKeyDown = (e) => {
if (
e.key === 'k' && (isMac ? e.metaKey : e.ctrlKey)
) {
e.preventDefault();
setOpen((prev) => !prev);
} else if (e.key === 'Escape') {
e.preventDefault();
setOpen(false);
}
};
document.addEventListener('keydown', handleKeyDown);
return () =>
document.removeEventListener(
'keydown',
handleKeyDown
);
});
return (
<div className="bg-linear-to-r from-indigo-500 to-violet-500 p-4 sm:p-8 h-[340px] rounded-lg flex items-center justify-center">
<DialogTrigger isOpen={isOpen} onOpenChange={setOpen}>
<Button className="inline-flex items-center justify-center rounded-xl bg-black/20 bg-clip-padding border border-white/20 px-3 py-2 font-medium font-[inherit] text-sm sm:text-base text-white hover:bg-black/30 pressed:bg-black/40 transition-colors cursor-default outline-hidden focus-visible:ring-2 focus-visible:ring-white/75">
<span className="block sm:hidden">
Tap to open
</span>
<span className="hidden sm:block">
Type{' '}
<kbd className="px-2 py-1 m-1 text-xs font-semibold border border-gray-200 rounded-lg">
{isMac ? '⌘' : 'Ctrl'}
</kbd>{' '}
+{' '}
<kbd className="px-2 py-1 m-1 text-xs font-semibold border border-gray-200 rounded-lg">
K
</kbd>{' '}
or press here to open
</span>
</Button>
<ModalOverlay
isDismissable
className={({ isEntering, isExiting }) => `
fixed inset-0 z-10 overflow-y-auto bg-black/25 flex min-h-full items-start sm:items-center justify-center p-4 text-center
`}
>
<Modal
className={({ isEntering, isExiting }) => `
`}
>
<Dialog className="outline-hidden relative">
<div className="flex flex-col gap-1 w-[95vw] sm:w-[500px] max-w-full rounded-xl bg-white shadow-lg p-2">
<Autocomplete filter={contains}>
<TextField
aria-label="Search commands"
className="flex flex-col px-3 py-2 rounded-md outline-none placeholder-white/70"
>
<Input
autoFocus
placeholder="Search commands…"
className="border-none py-2 px-3 leading-5 text-gray-900 bg-transparent outline-hidden text-base focus-visible:ring-2 focus-visible:ring-violet-500 rounded-lg"
/>
</TextField>
<Menu
items={commands}
className="mt-2 p-1 max-h-44 overflow-auto"
>
{({ label }) => (
<CommandItem>{label}</CommandItem>
)}
</Menu>
</Autocomplete>
</div>
</Dialog>
</Modal>
</ModalOverlay>
</DialogTrigger>
</div>
);
}
function CommandItem(props) {
return (
<MenuItem
{...props}
className="group flex w-full items-center rounded-md px-3 py-2 box-border outline-none cursor-default text-gray-900 hover:bg-violet-100 pressed:bg-violet-200 focus:bg-violet-500 focus:text-white"
/>
);
}
import {
Autocomplete,
Button,
Dialog,
DialogTrigger,
Input,
Menu,
MenuItem,
Modal,
ModalOverlay,
TextField,
useFilter
} from 'react-aria-components';
import {
useEffect,
useMemo,
useState
} from 'react';
function CommandPaletteExample() {
let commands = [
{
id: 'new-file',
label:
'Create new file…'
},
{
id: 'new-folder',
label:
'Create new folder…'
},
{
id: 'assign',
label: 'Assign to…'
},
{
id: 'assign-me',
label:
'Assign to me'
},
{
id: 'status',
label:
'Change status…'
},
{
id: 'priority',
label:
'Change priority…'
},
{
id: 'label-add',
label: 'Add label…'
},
{
id: 'label-remove',
label:
'Remove label…'
}
];
let [isOpen, setOpen] =
useState(false);
let { contains } =
useFilter({
sensitivity: 'base'
});
let isMac = useMemo(
() =>
/Mac/.test(
navigator
.platform
),
[]
);
useEffect(() => {
const handleKeyDown =
(e) => {
if (
e.key ===
'k' && (isMac
? e.metaKey
: e
.ctrlKey)
) {
e.preventDefault();
setOpen((
prev
) => !prev);
} else if (
e.key ===
'Escape'
) {
e.preventDefault();
setOpen(false);
}
};
document
.addEventListener(
'keydown',
handleKeyDown
);
return () =>
document
.removeEventListener(
'keydown',
handleKeyDown
);
});
return (
<div className="bg-linear-to-r from-indigo-500 to-violet-500 p-4 sm:p-8 h-[340px] rounded-lg flex items-center justify-center">
<DialogTrigger
isOpen={isOpen}
onOpenChange={setOpen}
>
<Button className="inline-flex items-center justify-center rounded-xl bg-black/20 bg-clip-padding border border-white/20 px-3 py-2 font-medium font-[inherit] text-sm sm:text-base text-white hover:bg-black/30 pressed:bg-black/40 transition-colors cursor-default outline-hidden focus-visible:ring-2 focus-visible:ring-white/75">
<span className="block sm:hidden">
Tap to open
</span>
<span className="hidden sm:block">
Type{' '}
<kbd className="px-2 py-1 m-1 text-xs font-semibold border border-gray-200 rounded-lg">
{isMac
? '⌘'
: 'Ctrl'}
</kbd>{' '}
+{' '}
<kbd className="px-2 py-1 m-1 text-xs font-semibold border border-gray-200 rounded-lg">
K
</kbd>{' '}
or press here
to open
</span>
</Button>
<ModalOverlay
isDismissable
className={(
{
isEntering,
isExiting
}
) => `
fixed inset-0 z-10 overflow-y-auto bg-black/25 flex min-h-full items-start sm:items-center justify-center p-4 text-center
`}
>
<Modal
className={(
{
isEntering,
isExiting
}
) => `
`}
>
<Dialog className="outline-hidden relative">
<div className="flex flex-col gap-1 w-[95vw] sm:w-[500px] max-w-full rounded-xl bg-white shadow-lg p-2">
<Autocomplete
filter={contains}
>
<TextField
aria-label="Search commands"
className="flex flex-col px-3 py-2 rounded-md outline-none placeholder-white/70"
>
<Input
autoFocus
placeholder="Search commands…"
className="border-none py-2 px-3 leading-5 text-gray-900 bg-transparent outline-hidden text-base focus-visible:ring-2 focus-visible:ring-violet-500 rounded-lg"
/>
</TextField>
<Menu
items={commands}
className="mt-2 p-1 max-h-44 overflow-auto"
>
{(
{
label
}
) => (
<CommandItem>
{label}
</CommandItem>
)}
</Menu>
</Autocomplete>
</div>
</Dialog>
</Modal>
</ModalOverlay>
</DialogTrigger>
</div>
);
}
function CommandItem(
props
) {
return (
<MenuItem
{...props}
className="group flex w-full items-center rounded-md px-3 py-2 box-border outline-none cursor-default text-gray-900 hover:bg-violet-100 pressed:bg-violet-200 focus:bg-violet-500 focus:text-white"
/>
);
}
Tailwind config#
This example uses the following plugins:
When using Tailwind v4, add them to your CSS:
@import "tailwindcss";
@plugin "tailwindcss-react-aria-components";
@plugin "tailwindcss-animate";
@import "tailwindcss";
@plugin "tailwindcss-react-aria-components";
@plugin "tailwindcss-animate";
@import "tailwindcss";
@plugin "tailwindcss-react-aria-components";
@plugin "tailwindcss-animate";
Tailwind v3
When using Tailwind v3, add the plugins to your tailwind.config.js
instead:
module.exports = {
// ...
plugins: [
require('tailwindcss-react-aria-components'),
require('tailwindcss-animate')
]
};
module.exports = {
// ...
plugins: [
require('tailwindcss-react-aria-components'),
require('tailwindcss-animate')
]
};
module.exports = {
// ...
plugins: [
require(
'tailwindcss-react-aria-components'
),
require(
'tailwindcss-animate'
)
]
};
Note: When using Tailwind v3, install tailwindcss-react-aria-components
version 1.x instead of 2.x.
Components#
TextField
A text field allows a user to enter a plain text value with a keyboard.
Menu
A menu displays a list of actions or options that a user can choose.