Hotkeys

Keyboard shortcuts for buttons, menu items and your own actions, written once as a string that is both shown and bound.

Sent 0 times, 0 drafts started. Press the keys shown on the buttons.

import * as React from "react"
import { Plus, Send } from "lucide-react"

When to use it

Use a hotkey for actions that people repeat all day: save, send, create, search, move between rows. A shortcut saves a trip to the mouse, and showing it on the button teaches it.

A shortcut should never be the only way to do something. People on a phone, or who cannot use a keyboard, need the button as well. Keep to a few, and use combinations with Mod, because a plain letter competes with typing.

The hotkey string

A hotkey is the modifiers and one key, joined with +: "Mod+S", "Mod+Shift+K", "Alt+N", "Escape". It ignores case and spaces. The same string is shown to people and bound to the key, so the two cannot drift apart.

Mod is the main modifier of the system: Cmd on a Mac, Ctrl everywhere else. Use it for most shortcuts, so one string works on every platform.

PartWhat you can write
ModifiersMod, Ctrl (Control), Alt (Option), Shift, Meta (Cmd)
Letters and digitsa to z, 0 to 9
Named keysEnter, Escape, Space, Tab, Backspace, Delete, the four arrow keys, Home, End, PageUp, PageDown, F1 to F12
Punctuation, . / ; ' [ ] - = \ and the backtick

The key + itself and sequences such as "g then h" are not supported. An invalid string binds nothing and is shown as typed, so a mistake is easy to see.

HotkeyMacWindows and Linux
Mod+K⌘ KCtrl + K
Mod+Shift+K⇧⌘ KCtrl + Shift + K
Mod+Enter⌘ ↵Ctrl + Enter
EscapeEscEsc

Import

import {
  useHotkey,
  useHotkeyLabel,
  useHotkeyTrigger,
  formatHotkey,
  parseHotkey,
} from "@hel/ui/hotkeys"

On components

Give a component a hotkey, and it shows the hint and handles the key. Pressing the key clicks the element, as if someone had clicked it. The onClick you already have is the action, so there is no second function to wire.

<Button hotkey="Mod+S" onClick={save}>Save</Button>
<DropdownMenuItem hotkey="Mod+D" onClick={duplicate}>Duplicate</DropdownMenuItem>

A disabled element does nothing, an element that is not on the page does nothing, and a menu item closes its menu, just like a click. A plain key or a Shift or Alt combination does not fire while someone types in a field, but a Mod combination does, so Mod+Enter can send from inside a text box. Holding the key does not repeat the action.

These components take hotkey: Button, SidebarMenuButton, DropdownMenuItem and ContextMenuItem, and the command and menubar items built on them. The sidebar toggle (shortcut, "b" by default) and the command palette (shortcut, "k" by default) use the same system.

Examples

Buttons

The shortcut works from anywhere on the page, for as long as the button is on it. In the preview at the top of this page, press Ctrl and Enter, or Alt and N, and watch the counters.

Your own actions

useHotkey(hotkey, handler, options) runs a function when the key is pressed. It needs no list of dependencies, because the latest handler is always used. Turn it off without removing it with enabled, which suits a key that only makes sense while something is open.

Quantity: 0

Alt + ↑Alt + ↓
import * as React from "react"

import { Button } from "@hel/ui/button"
OptionDefaultWhat it does
enabledtrueTurns the binding off.
preventDefaulttrueStops the browser's own action, such as saving the page on Mod+S.
allowInInputsonly with Mod, Ctrl or MetaLets the key fire while a text field has focus.
allowRepeatfalseLets holding the key repeat the action.

Pass undefined as the hotkey and the hook does nothing, so it can stay in your code unconditionally. To show a hint without binding anything, use Kbd with hotkey.

A menu removes its items from the page when it closes, so the hotkey of an item works only while the menu is open, like the shortcuts in a desktop application. Open this one, then press the keys shown.

Last action: Nothing yet

import * as React from "react"
import { Copy, Trash2 } from "lucide-react"

If a shortcut must also work when the menu is closed, bind it where the action lives with useHotkey, and show the same text on the item with kbd.

useHotkey("Mod+D", duplicate)
 
<DropdownMenuItem kbd={formatHotkey("Mod+D")} onClick={duplicate}>
  Duplicate
</DropdownMenuItem>

Prefer Mod combinations on menu items, because a single letter competes with typing to jump to an item.

Good to know

  • Two bindings on the same key both fire, and in development a one-time warning in the console says so. Mod+K and Ctrl+K count as the same key on Windows.
  • Keys pressed while an input method (IME) is composing text are ignored.
  • The first render on the client matches the server, because the platform is treated as "other" until the page has loaded, and the labels then switch to ⌘ on a Mac.
  • To hide every shortcut hint in an app, set shortcuts.visible to false in the config. The keys still work.

Props

Components with a hotkey

PropTypeDefault
hotkeya hotkey stringnone
kbdstringthe label of hotkey

kbd only shows text and binds nothing. If both are given, kbd is the text shown.

Functions

FunctionUse
useHotkey(hotkey, handler, options?)Runs a function when the key is pressed.
useHotkeyLabel(hotkey)The text to show for the platform, or undefined for no hotkey.
useHotkeyTrigger(hotkey, { enabled, ref })Binds a key to an element, and returns the ref to put on it.
formatHotkey(hotkey, platform?)The label as plain text. Works on a server.
parseHotkey(hotkey)The parts of a hotkey. Works on a server.