06interaction

Keyboard

The scoped keyboard layer — remappable mnemonics via the keymap prop, the ? sheet that can never lie, and what is deliberately fixed.

One table drives the whole keyboard layer: the dispatcher fires from it and the ? shortcuts sheet renders from it, so a remapped key can never make the sheet lie. Everything is scoped to the component — a calendar dropped into your page claims no keys it hasn't earned.

The default mnemonics

ActionDefault keyWhat it does
todaytBack to today.
view-day / view-week / view-month / view-year / view-agendad / w / m / y / aThe five view letters.
view-3days33-day view.
prev-period / next-periodunboundShip unbound, for the h/l crowd to claim.
createcNew event at the next hour.
shortcuts?The keyboard shortcuts sheet.

With an event selected, 15 always recolor it — digits are dual-use, checked before the keymap dispatch, so recoloring is not remappable-away. With nothing selected, a digit fires whatever binding claims it: binding a view to "3" works everywhere except mid-selection.

Remapping — the keymap prop

keymap takes host overrides exactly as a settings screen stores them: only the actions the user touched, null meaning "unbound on purpose". A binding is one unmodified key, stored as its lowercased KeyboardEvent.key value — no chord syntax, because every remappable action is a mnemonic and mnemonics are single keys.

import type { Keymap } from "@/components/kloq";

const keymap: Keymap = {
  today: "g",          // rebind
  "view-agenda": null, // unbind on purpose
  "prev-period": "h",  // bind the shipped-unbound pair
  "next-period": "l",
};

<Kloq keymap={keymap} onCustomizeKeybinds={openKeybindEditor} />

An override claims its key: any other action whose effective key collides — including an untouched default — comes out unbound, because two actions on one key is not a tie to break at keydown time. Unknown ids and unusable keys are dropped rather than rejected, so a keymap saved by a newer build degrades instead of bricking the keyboard. onCustomizeKeybinds puts a "Customize shortcuts…" button in the ? sheet; omit it and the button isn't rendered — the package ships no keybind editor of its own.

What is deliberately not remappable

The conventions (⌘Z ⌘K ⌘C ⌘X ⌘V ⌘A ⌘D) mean the same thing in every app on the machine — a calendar that lets you point ⌘Z somewhere else is broken in a way no preference can excuse. Arrows, the // arrow chords, and Escape are structural: they mirror the drag engine and selection model, and remapping one without the others produces a keyboard that half-works. Those keys are also reserved — a binding may never claim them.

navigationKeys is the one switch on the fixed layer: whether / move by the whole visible range (default) or by one day. PageUp/PageDown always step the range.

Who owns a keystroke

A window-level listener that preventDefault()s arrows is fine for an app and hostile for a component — it would break every listbox, menu and tab list on the host page. So kloq stands down whenever the target sits inside anything that owns its own keyboard model: text fields, comboboxes, menus, grids, sliders, an IME composition. When nothing has focus at all — the common case after clicking the non-focusable grid — the calendar claims the keys only if the last pointer press landed inside it: a freshly-mounted calendar you've never touched claims nothing.

globalShortcuts widens exactly three page-level keys — ⌘K, ⌘Z, ⌥D — to fire even when focus is outside the calendar. Off by default: an embedded component has no business claiming page-wide keys. Everything else is always scoped regardless of the flag.

on this page