React AriaExamples

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(
    () =>
      typeof navigator === 'undefined'
        ? false
        : /mac(os|intosh)/i.test(navigator.userAgent),
    []
  );

  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 }) => `
          absolute top-0 left-0 w-full h-(--page-height) z-10 bg-black/25
          ${isEntering ? 'animate-in fade-in duration-300 ease-out' : ''}
          ${isExiting ? 'animate-out fade-out duration-200 ease-in' : ''}
        `}
        >
          <div className="sticky top-0 left-0 w-full h-(--visual-viewport-height) flex items-start sm:items-center justify-center p-4 box-border text-center">
            <Modal
              className={({ isEntering, isExiting }) => `
              ${isEntering ? 'animate-in zoom-in-95 ease-out duration-300' : ''}
              ${isExiting ? 'animate-out zoom-out-95 ease-in duration-200' : ''}
            `}
            >
              <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>
          </div>
        </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(
    () =>
      typeof navigator === 'undefined'
        ? false
        : /mac(os|intosh)/i.test(navigator.userAgent),
    []
  );

  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 }) => `
          absolute top-0 left-0 w-full h-(--page-height) z-10 bg-black/25
          ${
            isEntering
              ? 'animate-in fade-in duration-300 ease-out'
              : ''
          }
          ${
            isExiting
              ? 'animate-out fade-out duration-200 ease-in'
              : ''
          }
        `}
        >
          <div className="sticky top-0 left-0 w-full h-(--visual-viewport-height) flex items-start sm:items-center justify-center p-4 box-border text-center">
            <Modal
              className={({ isEntering, isExiting }) => `
              ${
                isEntering
                  ? 'animate-in zoom-in-95 ease-out duration-300'
                  : ''
              }
              ${
                isExiting
                  ? 'animate-out zoom-out-95 ease-in duration-200'
                  : ''
              }
            `}
            >
              <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>
          </div>
        </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(
    () =>
      typeof navigator ===
          'undefined'
        ? false
        : /mac(os|intosh)/i
          .test(
            navigator
              .userAgent
          ),
    []
  );

  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
            }
          ) => `
          absolute top-0 left-0 w-full h-(--page-height) z-10 bg-black/25
          ${
            isEntering
              ? 'animate-in fade-in duration-300 ease-out'
              : ''
          }
          ${
            isExiting
              ? 'animate-out fade-out duration-200 ease-in'
              : ''
          }
        `}
        >
          <div className="sticky top-0 left-0 w-full h-(--visual-viewport-height) flex items-start sm:items-center justify-center p-4 box-border text-center">
            <Modal
              className={(
                {
                  isEntering,
                  isExiting
                }
              ) => `
              ${
                isEntering
                  ? 'animate-in zoom-in-95 ease-out duration-300'
                  : ''
              }
              ${
                isExiting
                  ? 'animate-out zoom-out-95 ease-in duration-200'
                  : ''
              }
            `}
            >
              <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>
          </div>
        </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.