Skip to content

useHotkeys

키보드 단축키를 선언적으로 등록하는 Hook입니다. React(CSR), Next.js Pages Router, App Router에서 모두 SSR-safe하게 동작합니다.

  • cross-OS: mod 토큰이 macOS에서 (Meta), 그 외에서는 Ctrl로 해석됩니다.
  • KeyboardEvent.key 기준 매칭: 키보드 레이아웃과 무관하게 사용자가 보는 문자로 매칭하며, 글자는 대소문자를 구분하지 않습니다. modifier는 정확히 일치해야 합니다(mod+kk 단독·mod+shift+k에 발화하지 않음).
  • 비라틴 IME 폴백: 한글 입력 중 ⌃J'ㅓ'로 보고되는 것처럼 event.key가 **비라틴 문자(한글·키릴 등)**로 보고되는 경우, ctrl/alt/meta가 포함된 조합에 한해 물리 키(event.code)로 보조 매칭합니다. 라틴 자판(AZERTY·터키어 등)의 입력과 Dead 같은 조합 키에는 발동하지 않아 오발화가 없습니다. 단, shift 단독 조합(shift+x)과 단일 키(j)는 타이핑과 겹칠 수 있어 폴백 대상이 아니므로 비라틴 IME 중에는 매칭되지 않습니다.
  • form 가드 / IME 안전: 기본적으로 input/textarea/select·contenteditable 포커스 중에는 발화하지 않으며, 한글/일문/중문 등 IME 조합 중(isComposing)에도 발화하지 않습니다.
  • focus-scope: 반환된 ref를 요소에 부착하면 그 요소(또는 자식)에 포커스가 있을 때만 발화합니다.

Return

  • ref: focus-scope에 사용할 ref 객체입니다. 요소에 부착하면 그 요소에 포커스가 있을 때만 단축키가 발화합니다. 부착하지 않으면 전역(window)에서 동작하므로, 전역 단축키라면 사용하지 않아도 됩니다.

Parameters

이름타입설명
keysstring | string[]단축키 문자열 또는 배열. 예: 'mod+k', ['mod+k', 'ctrl+k'], 'shift+?'
handler(event: KeyboardEvent) => void단축키가 매칭됐을 때 실행할 콜백. 원본 KeyboardEvent를 인자로 받습니다.
optionsUseHotkeysOptions동작 옵션 (아래)

키 문법

  • modifier: mod(OS별 /Ctrl), meta/cmd/command, ctrl/control, alt/option, shift. 구분자는 +.
  • 특수키 alias: escEscape, enter/return, space, up/down/left/rightArrow*, delDelete, plus+, comma,. 콤마(mod+,)·플러스(ctrl++)는 직접 써도 됩니다.
  • 여러 조합을 한 handler에 매핑하려면 배열을 사용합니다: ['mod+k', 'ctrl+k'].
  • shift가 필요한 문자는 결과 문자 기준으로 작성합니다. 예: ?'shift+?'.

Options

옵션타입기본값설명
enabledbooleantruefalse면 이벤트 리스너 자체를 바인딩하지 않습니다.
eventType'keydown' | 'keyup''keydown'단축키를 감지할 이벤트 종류
preventDefaultboolean | ((event: KeyboardEvent) => boolean)false매칭 시 event.preventDefault() 호출 여부. 함수면 동적으로 결정.
enableOnFormTagsboolean | ('input' | 'textarea' | 'select')[]falseform 태그 포커스 중 발화 허용. 배열이면 지정한 태그에서만 허용.
enableOnContentEditablebooleanfalsecontenteditable 포커스 중 발화 허용

Usage

tsx
import { useHotkeys } from '@teamsparta/react';

function CommandPalette() {
  const [isOpen, setIsOpen] = useState(false);

  // 전역: mac은 ⌘K, 그 외는 Ctrl+K
  useHotkeys('mod+k', () => setIsOpen((prev) => !prev), { preventDefault: true });

  // 조건부: 열려 있을 때만 Esc로 닫기
  useHotkeys('escape', () => setIsOpen(false), { enabled: isOpen });

  return isOpen ? <Palette onClose={() => setIsOpen(false)} /> : null;
}

Example

focus-scope (요소에 포커스가 있을 때만)

tsx
function Editor({ onSave }: { onSave: () => void }) {
  // section 내부에 포커스가 있을 때만 ⌘S/Ctrl+S 동작, 브라우저 저장 동작은 막음
  const ref = useHotkeys<HTMLElement>('mod+s', onSave, { preventDefault: true });

  return (
    <section ref={ref} tabIndex={-1}>
      <textarea />
    </section>
  );
}

여러 조합 + 검색창에서도 동작

tsx
function SearchBox() {
  const inputRef = useRef<HTMLInputElement>(null);

  // input 포커스 중에도 동작하도록 enableOnFormTags 허용
  useHotkeys(['mod+k', 'ctrl+k'], () => inputRef.current?.focus(), {
    enableOnFormTags: true,
    preventDefault: true,
  });

  return <input ref={inputRef} placeholder="검색" />;
}

접근성(WCAG 2.1.4): 인쇄 가능한 문자 하나만으로 된 단축키('k' 등)는 접근성 기준을 위반할 수 있습니다. modifier를 포함('mod+k')하거나 focus-scope ref로 범위를 제한해 사용하세요.

플랫폼 주의사항: ① macOS는 ⌘를 누르고 있는 동안 다른 키의 keyup 이벤트를 전달하지 않으므로 eventType: 'keyup'에는 ⌘/mod 조합을 피하세요. ② ⌘W·⌘Q 같은 브라우저 예약 단축키는 preventDefault로도 가로챌 수 없습니다.