Giới thiệu TanStack Hotkeys

TanStack Hotkeys là thư viện giúp bạn thêm phím tắt bàn phím vào ứng dụng web. Bạn dùng nó khi dashboard, editor, command palette hoặc trang quản trị cần thao tác nhanh bằng bàn phím. TanStack Hotkeys phù hợp với React và Next.js vì thư viện cung cấp hook như useHotkey, useHotkeySequence, useHotkeyRecorder, useKeyHold và các hàm hiển thị shortcut như formatForDisplay.
TanStack Hotkeys đang được ghi nhãn alpha trên tài liệu chính thức. Nếu dùng cho production, hãy kiểm tra kỹ hành vi phím tắt, khả năng truy cập và thay đổi API khi nâng phiên bản.

Khi nào nên dùng

Trường hợpVí dụ
Lưu nhanh dữ liệuMod+S để lưu form hoặc tài liệu
Mở command paletteMod+K để mở thanh lệnh
Đóng dialog hoặc panelEscape để đóng modal
Điều hướng kiểu editorG rồi G để về đầu trang
Tùy chỉnh shortcutCho người dùng tự ghi phím tắt trong trang settings
Hiển thị trạng thái phímGiữ Shift để đổi hành động xóa

Tính năng chính

Tính năngCông dụng
Type-safe hotkey stringsKhai báo shortcut bằng chuỗi như Mod+S hoặc Escape
Cross-platform ModTự dùng Command trên macOS và Control trên Windows/Linux
Keyboard sequencesBắt chuỗi phím bấm liên tiếp như G, G
Hotkey recordingCho người dùng ghi lại shortcut của riêng họ
Key state trackingBiết phím nào đang được giữ
Scope theo elementChỉ kích hoạt shortcut trong một vùng giao diện
Display formattingHiển thị shortcut đúng theo hệ điều hành
Cleanup tự độngHook tự dọn listener khi component unmount

Cài đặt

1

Cài package React

Chạy lệnh sau trong dự án Next.js:
npm install @tanstack/react-hotkeys
2

Dùng trong client component

Các hook của TanStack Hotkeys cần chạy trong browser, nên component phải có directive use client.
'use client'

Ví dụ cơ bản

Ví dụ dưới đây thêm shortcut Mod+S. Trên macOS, người dùng bấm Command+S. Trên Windows/Linux, người dùng bấm Control+S.
'use client'

import { formatForDisplay, useHotkey } from '@tanstack/react-hotkeys'

type SaveShortcutProps = {
  onSave: () => void
}

export function SaveShortcut({ onSave }: SaveShortcutProps) {
  useHotkey('Mod+S', () => {
    onSave()
  })

  return (
    <button type="button" onClick={onSave}>
      Lưu <kbd>{formatForDisplay('Mod+S')}</kbd>
    </button>
  )
}
useHotkey mặc định gọi preventDefaultstopPropagation. Vì vậy Mod+S sẽ chạy hàm onSave thay vì mở hộp thoại lưu trang của trình duyệt.

Scope shortcut theo vùng giao diện

Khi shortcut chỉ nên chạy trong một panel hoặc form cụ thể, truyền target bằng ref.
'use client'

import { useRef } from 'react'
import { useHotkey } from '@tanstack/react-hotkeys'

type CustomerPanelProps = {
  onClose: () => void
}

export function CustomerPanel({ onClose }: CustomerPanelProps) {
  const panelRef = useRef<HTMLDivElement>(null)

  useHotkey('Escape', onClose, {
    target: panelRef,
  })

  return (
    <div ref={panelRef} tabIndex={0}>
      <h2>Chi tiết khách hàng</h2>
      <p>Bấm Escape khi panel đang focus để đóng.</p>
    </div>
  )
}

Bật tắt shortcut theo trạng thái

Dùng enabled khi shortcut chỉ hợp lệ trong một trạng thái nhất định.
'use client'

import { useHotkey } from '@tanstack/react-hotkeys'

type DeleteDialogProps = {
  open: boolean
  onConfirm: () => void
  onClose: () => void
}

export function DeleteDialog({ open, onConfirm, onClose }: DeleteDialogProps) {
  useHotkey('Enter', onConfirm, { enabled: open })
  useHotkey('Escape', onClose, { enabled: open })

  if (!open) {
    return null
  }

  return (
    <div role="dialog" aria-modal="true">
      <p>Bạn có chắc muốn xóa bản ghi này?</p>
      <button type="button" onClick={onConfirm}>
        Xóa
      </button>
      <button type="button" onClick={onClose}>
        Hủy
      </button>
    </div>
  )
}

Dùng chuỗi phím

useHotkeySequence dùng cho các lệnh cần nhiều bước, ví dụ phím tắt kiểu Vim.
'use client'

import { useHotkeySequence } from '@tanstack/react-hotkeys'

export function PageShortcuts() {
  useHotkeySequence(['G', 'G'], () => {
    window.scrollTo({ top: 0, behavior: 'smooth' })
  })

  useHotkeySequence(['G', 'Shift+G'], () => {
    window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' })
  })

  return null
}
Người dùng phải bấm đúng thứ tự trong khoảng thời gian cho phép. Bạn có thể truyền timeout nếu muốn rút ngắn hoặc kéo dài thời gian chờ.
useHotkeySequence(['D', 'D'], deleteSelectedItem, {
  timeout: 500,
})

Ghi shortcut do người dùng chọn

useHotkeyRecorder phù hợp cho trang settings, nơi người dùng tự chọn shortcut.
'use client'

import { formatForDisplay, useHotkeyRecorder } from '@tanstack/react-hotkeys'

type ShortcutRecorderProps = {
  onChange: (hotkey: string) => void
}

export function ShortcutRecorder({ onChange }: ShortcutRecorderProps) {
  const recorder = useHotkeyRecorder({
    onRecord: onChange,
  })

  return (
    <button
      type="button"
      onClick={recorder.isRecording ? recorder.stopRecording : recorder.startRecording}
    >
      {recorder.isRecording
        ? 'Bấm tổ hợp phím...'
        : recorder.recordedHotkey
          ? formatForDisplay(recorder.recordedHotkey)
          : 'Ghi shortcut'}
    </button>
  )
}

Theo dõi phím đang được giữ

useKeyHold giúp UI phản hồi khi người dùng đang giữ một phím cụ thể.
'use client'

import { useKeyHold } from '@tanstack/react-hotkeys'

type DeleteButtonProps = {
  onMoveToTrash: () => void
  onDeleteForever: () => void
}

export function DeleteButton({ onMoveToTrash, onDeleteForever }: DeleteButtonProps) {
  const isShiftHeld = useKeyHold('Shift')

  return (
    <button
      type="button"
      onClick={isShiftHeld ? onDeleteForever : onMoveToTrash}
    >
      {isShiftHeld ? 'Xóa vĩnh viễn' : 'Chuyển vào thùng rác'}
    </button>
  )
}

Lưu ý khi dùng trong Next.js

  • Chỉ gọi hook trong client component.
  • Dùng Mod thay vì tự tách ControlMeta.
  • Không ghi đè shortcut quen thuộc của trình duyệt nếu không có lý do rõ ràng.
  • Luôn cho người dùng thấy shortcut bằng formatForDisplay.
  • Kiểm tra shortcut khi focus đang nằm trong input, textarea, select hoặc vùng contentEditable.
  • Với dialog, modal và panel, ưu tiên dùng enabled hoặc target để giới hạn phạm vi.
Với dashboard quản trị, hãy bắt đầu bằng các shortcut ít rủi ro như Mod+K, Mod+S, Escape, rồi mới thêm các shortcut một phím như G hoặc D.

Prompt gợi ý cho Claude Code

Thêm TanStack Hotkeys vào dự án Next.js hiện tại.
Tạo các shortcut sau:
- Mod+K mở command palette.
- Mod+S lưu form hiện tại.
- Escape đóng dialog đang mở.

Yêu cầu:
- Dùng @tanstack/react-hotkeys.
- Các hook phải nằm trong client component.
- Hiển thị shortcut bằng formatForDisplay.
- Không làm shortcut chạy khi đang nhập liệu nếu hành vi đó gây lỗi UX.

Tài liệu tham khảo