useHotkeys
키보드 단축키를 선언적으로 등록하는 Hook입니다. React(CSR), Next.js Pages Router, App Router에서 모두 SSR-safe하게 동작합니다.
- cross-OS:
mod토큰이 macOS에서⌘(Meta), 그 외에서는Ctrl로 해석됩니다. KeyboardEvent.key기준 매칭: 키보드 레이아웃과 무관하게 사용자가 보는 문자로 매칭하며, 글자는 대소문자를 구분하지 않습니다. modifier는 정확히 일치해야 합니다(mod+k는k단독·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
| 이름 | 타입 | 설명 |
|---|---|---|
keys | string | string[] | 단축키 문자열 또는 배열. 예: 'mod+k', ['mod+k', 'ctrl+k'], 'shift+?' |
handler | (event: KeyboardEvent) => void | 단축키가 매칭됐을 때 실행할 콜백. 원본 KeyboardEvent를 인자로 받습니다. |
options | UseHotkeysOptions | 동작 옵션 (아래) |
키 문법
- modifier:
mod(OS별⌘/Ctrl),meta/cmd/command,ctrl/control,alt/option,shift. 구분자는+. - 특수키 alias:
esc→Escape,enter/return,space,up/down/left/right→Arrow*,del→Delete,plus→+,comma→,. 콤마(mod+,)·플러스(ctrl++)는 직접 써도 됩니다. - 여러 조합을 한 handler에 매핑하려면 배열을 사용합니다:
['mod+k', 'ctrl+k']. shift가 필요한 문자는 결과 문자 기준으로 작성합니다. 예:?는'shift+?'.
Options
| 옵션 | 타입 | 기본값 | 설명 |
|---|---|---|---|
enabled | boolean | true | false면 이벤트 리스너 자체를 바인딩하지 않습니다. |
eventType | 'keydown' | 'keyup' | 'keydown' | 단축키를 감지할 이벤트 종류 |
preventDefault | boolean | ((event: KeyboardEvent) => boolean) | false | 매칭 시 event.preventDefault() 호출 여부. 함수면 동적으로 결정. |
enableOnFormTags | boolean | ('input' | 'textarea' | 'select')[] | false | form 태그 포커스 중 발화 허용. 배열이면 지정한 태그에서만 허용. |
enableOnContentEditable | boolean | false | contenteditable 포커스 중 발화 허용 |
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로도 가로챌 수 없습니다.
