videorc/ui

Accelerator

Parses Electron accelerators such as CmdOrCtrl+Shift+J and prints them the way each platform's menus do, with aria-keyshortcuts to match.

Installation

$ pnpm dlx shadcn@latest add @videorc/accelerator

Usage

import {
  acceleratorAriaKeyShortcuts,
  acceleratorLabel,
} from "@/lib/accelerator";
acceleratorLabel("CmdOrCtrl+Shift+J", "darwin"); // "⇧⌘J"
acceleratorLabel("CmdOrCtrl+Shift+J", "win32"); // "Ctrl+Shift+J"
acceleratorAriaKeyShortcuts("CmdOrCtrl+K", "darwin"); // "Meta+K"

Every function takes the platform explicitly, as Node's process.platform names it ("darwin", "win32", "linux"). It's pure, with no DOM and no React, so main, preload and the renderer can all share it.

In components, reach for ShortcutKeys, which draws the key chip, or for shortcutLabel from platform, which fills in the platform for you.

How shortcuts read

AcceleratormacOSWindows and Linux
CmdOrCtrl+K⌘KCtrl+K
CmdOrCtrl+Shift+J⇧⌘JCtrl+Shift+J
Shift+CmdOrCtrl+N⇧⌘NCtrl+Shift+N
Ctrl+Alt+Shift+Cmd+R⌃⌥⇧⌘RCtrl+Alt+Shift+R
CmdOrCtrl+Enter⌘↵Ctrl+Enter
Shift+Backspace⇧⌫Shift+Backspace
Delete⌦Delete
EscapeEscEsc
SpaceSpaceSpace
Super+F13⌘F13Win+F13
Alt⌥Alt

Behavior

  • One notation per platform. On macOS: glyphs in the system's order (⌃⌥⇧⌘) with no separator, as macOS menus print them. On Windows and Linux: words in Ctrl, Alt, Shift, Win order, joined by "+", and never a Mac glyph. The order you write the accelerator in doesn't matter.
  • Every Electron spelling reads, in any case: Command, Cmd, CommandOrControl, CmdOrCtrl, Control, Ctrl, Alt, Option, AltGr, Shift, Super and Meta. Return reads as Enter and Esc as Escape, letters are uppercased, and f5 becomes F5.
  • CmdOrCtrl is ⌘ on macOS and Ctrl elsewhere. Command reads as Ctrl off macOS too: Electron ignores it there, so Ctrl is what it has always meant. Super and Meta are ⌘ on macOS and Win elsewhere.
  • One saved spelling. parseAccelerator returns modifiers in a fixed order (Cmd, Ctrl, Alt, Shift, Super), so one binding always has one spelling, which keeps duplicate checks and diffs honest.
  • Exactly one key. A value with no key, or with two (Cmd+A+B), doesn't parse: parseAccelerator returns null, and acceleratorLabel prints the value unchanged. An empty or missing value prints "".
  • A modifier alone (Shift, Alt) prints as a hint for a drag, such as "⇧ constrains".
  • aria-keyshortcuts names the real keys, in UI Events names: Meta on macOS and Control elsewhere, ArrowUp for Up. Modifiers come in Control, Alt, Shift, Meta order.
  • No platform reads as Windows and Linux. Passing undefined prints Ctrl. Use platform to fill in the current one.
  • commandModifierHeld is true for ⌘ alone on macOS and Ctrl alone elsewhere, so an app shortcut on ⌘K leaves ⌃K to text editing on a Mac. It takes Electron's Input (meta, control); map a DOM event's metaKey and ctrlKey onto it, or use isCommandKey from platform.

API reference

ExportSignatureReturns
acceleratorLabel(accelerator: string | undefined, platform: string | undefined) => stringThe shortcut as one string: "⇧⌘J", "Ctrl+Shift+J"
acceleratorDisplayKeys(accelerator: string | undefined, platform: string | undefined) => string[]Its parts in display order: ["⇧", "⌘", "J"], ["Ctrl", "Shift", "J"]
acceleratorAriaKeyShortcuts(accelerator: string | undefined, platform: string | undefined) => stringThe aria-keyshortcuts value, or "" when nothing parses
acceleratorModifiersLabel(modifiers: readonly AcceleratorModifier[], platform: string | undefined) => stringModifiers held mid-press, with no key yet: "⇧⌘", "Ctrl+Shift"
acceleratorModifierLabels(modifiers: readonly AcceleratorModifier[], platform: string | undefined) => string[]The same, as separate labels
parseAccelerator(value: string | undefined, platform: string | undefined) => ParsedAccelerator | nullThe modifiers and the key, or null
commandModifierHeld(input: { meta: boolean; control: boolean }, platform: string | undefined) => booleanWhether the platform's command modifier is down
MODIFIER_ORDERreadonly AcceleratorModifier[]The saved order: Cmd, Ctrl, Alt, Shift, Super
type AcceleratorModifier = "Cmd" | "Ctrl" | "Alt" | "Shift" | "Super";

interface ParsedAccelerator {
  modifiers: AcceleratorModifier[];
  key: string;
}