본문으로 건너뛰기
오버레이 / command-palette

CommandPalette

⌘K 스타일 검색·명령 팔레트 오버레이.
오버레이command-paletteStatic specimencommand palettecmdk커맨드 팔레트명령
01 · Specimen

먼저 보고, 그다음 계약을 읽습니다

⌘K 스타일 검색·명령 팔레트 오버레이.

Workbench 불러오는 중…
Component intent의도와 경계 읽기

document.body portal의 viewport 모달 combobox — 입력한 질의로 items 를 label·keywords·group 기준 부분일치 필터하고, group 별로 묶어 listbox 로 보여준다. open 상태는 소비자가 제어(controlled)하며 선택 시 onSelect + onClose 가 함께 불린다. 포커스 트랩·이전 포커스 복원·aria-activedescendant 로 WAI-ARIA dialog+combobox 패턴을 직접 구현한다. 항목은 CmdItem({ id, label, keywords?, meta?, group? }) 배열로 주입한다.

02 · Use

예제

SSOT에 등록된 실제 API 기준 snippet입니다. standalone 배지만 독립 실행 단위이며, fragment는 주변 state·handler 문맥을 생략합니다.

01기본 (controlled)tsxfragment
기본 (controlled)
import { useState } from "react";import { CommandPalette, type CmdItem } from "@axe/ui";const items: CmdItem[] = [  { id: "new", label: "새 문서", group: "문서", meta: "⌘N" },  { id: "find", label: "검색", keywords: "find grep", group: "문서", meta: "⌘F" },  { id: "theme", label: "테마 전환", group: "설정" },];export function Launcher() {  const [open, setOpen] = useState(false);  return (    <>      <button onClick={() => setOpen(true)}>명령 팔레트 (⌘K)</button>      <CommandPalette        open={open}        items={items}        ariaLabel="문서 명령 검색"        placeholder="명령 검색…"        onClose={() => setOpen(false)}        onSelect={(item) => console.log(item.id)}      />    </>  );}
02CSS-only (비-React 정적 마크업)htmlfragment
CSS-only (비-React 정적 마크업)
<div class="axe-cmdk-backdrop">  <div class="axe-cmdk" role="dialog" aria-modal="true" aria-label="문서 명령 검색">    <div class="axe-cmdk__search-row">    <input      class="axe-cmdk__input"      role="combobox"      aria-label="문서 명령 검색어"      aria-expanded="true"      aria-autocomplete="list"      aria-controls="cmdk-list"      placeholder="검색…"    />      <button class="axe-cmdk__close" type="button" aria-label="검색 닫기">Esc</button>    </div>    <div id="cmdk-list" class="axe-cmdk__list" role="listbox">      <div class="axe-cmdk__group">        <div class="axe-cmdk__group-label">문서</div>        <div class="axe-cmdk__item" role="option" aria-selected="true">          <span>새 문서</span><span class="axe-cmdk__item-meta">⌘N</span>        </div>        <div class="axe-cmdk__item" role="option" aria-selected="false">          <span>검색</span><span class="axe-cmdk__item-meta">⌘F</span>        </div>      </div>    </div>  </div></div>
03 · React

Props

TSX 소스가 진실입니다. 주요 export 컴포넌트의 public API만 노출합니다.

이름타입필수기본값설명
openboolean필수열림 여부. 소비자가 제어(false 면 null 반환 — 언마운트).
onClose() => void필수닫기 요청 — Esc·backdrop 클릭·항목 선택 후 호출.
itemsCmdItem[]필수후보 목록. CmdItem = { id, label, keywords?, meta?, group? }.
onSelect(item: CmdItem) => void필수항목 확정 시 호출(뒤이어 onClose 도 실행됨).
placeholderstring"검색…"검색 input placeholder.
ariaLabelstring"명령 검색"dialog의 접근성 이름. 검색 input 이름도 이 값에서 파생.
classNamestring루트 backdrop(.axe-cmdk-backdrop)에 덧대는 클래스.
04 · Any stack

.axe-* 클래스 계약

React 밖에서도 같은 표면을 그리는 공개 계약입니다. stable은 minor 버전 안에서 이름을 바꾸지 않습니다.

클래스안정성용도
.axe-cmdk-backdropstable화면 전체 backdrop — 바깥 클릭 시 닫힘.
.axe-cmdkstable떠 있는 role=dialog 패널(포커스 트랩 컨테이너).
.axe-cmdk__search-rowstable검색 input + 명시적 닫기 버튼 행.
.axe-cmdk__inputstable상단 검색 input(role=combobox).
.axe-cmdk__closestable검색 닫기 버튼(Esc 힌트 포함).
.axe-cmdk__liststable결과 스크롤 영역(role=listbox).
.axe-cmdk__groupstable동일 group 항목 묶음 래퍼.
.axe-cmdk__group-labelstablegroup 헤더 라벨(group 있을 때만).
.axe-cmdk__itemstable항목 행(role=option). 강조는 [aria-selected="true"]·:hover 로.
.axe-cmdk__item-metastable항목 우측 메타(단축키 등, mono).
.axe-cmdk__emptystable결과 없음 안내.
비-React 소비 노트
필터·포커스 트랩·활성 이동은 JS 로직 — CSS-only 로는 정적 표시만 재현된다. 항목 강조는 [aria-selected="true"] 속성으로 그려지므로, 비-React 로 쓸 땐 활성 option 에 aria-selected 를 직접 토글해야 한다.
05 · Inclusive

접근성

키보드, ARIA, 구현 노트를 함께 검토합니다.

Keyboard
  • ↑/↓ 활성 항목 이동(범위 clamp)
  • Enter 활성 항목 선택(한글 IME 조합 중 Enter 는 글자 확정으로 무시)
  • Esc 닫기
  • Tab/Shift+Tab 팔레트 내부 순환(포커스 트랩 — 밖으로 탈출 금지)
ARIA

패널 = role=dialog + aria-modal=true + aria-label. input = role=combobox + 접근 이름 + aria-expanded=true + aria-autocomplete=list + aria-controls(listbox) + aria-activedescendant(활성 option id). 리스트 = role=listbox, 그룹 = role=group + aria-labelledby, 항목 = role=option + aria-selected.

Notes

document.body portal이라 transformed/backdrop-filter 조상에 fixed modal이 갇히지 않는다. 열릴 때 같은 window의 다른 CommandPalette를 닫아 단일 modal만 유지하고, 최초 opener 포커스를 보존한 채 rAF로 input에 포커스한다. 마지막 palette가 닫힐 때만 body scroll lock을 풀고 최초 opener로 포커스를 복원한다. 방향키/Enter 선택은 input에서만 가로채므로 닫기 버튼의 네이티브 Enter를 침범하지 않는다. 활성 항목은 scrollIntoView(block:nearest)로 시야 유지. ⌘K 전역 단축키는 컴포넌트가 등록하지 않음 — open 토글은 소비자 몫.

06 · Judgment

권장 · 지양

권장
  • open/닫기 상태와 ⌘K 단축키는 소비자가 관리(예 useHotkeys)한다.
  • meta 에 단축키를, group 으로 항목을 의미 단위로 묶는다.
  • 검색 보강이 필요하면 keywords 에 동의어(영/한)를 넣는다.
지양
  • 긴 목록에 무거운 onSelect 를 두지 말 것 — 선택 직후 onClose 로 닫힌다.
  • backdrop 을 다른 오버레이 위에 겹치지 말 것(단일 모달 전제).
검색창을 열면 컴포넌트 인덱스를 불러옵니다.