Built-in keymap addons

@opentui/keymap keeps the engine bare. The built-in addon packages install parser stages, field compilers, diagnostics, disambiguation and host-specific helpers on top of the public registration APIs.

If you want to build your own addon, see Custom Addons.

Every addon returns a disposer.

import * as addons from "@opentui/keymap/addons"
import * as opentuiAddons from "@opentui/keymap/addons/opentui"

Universal addons

Defaults

ExportDescription
defaultBindingParserShared parser for string key syntax
defaultEventMatchResolverShared event matcher for canonical normalized strokes
registerDefaultBindingParser()Appends defaultBindingParser
registerDefaultEventMatchResolver()Appends defaultEventMatchResolver
registerDefaultKeys()Installs both default stages together

The shared default parser accepts single-stroke named keys, modifier chords, literal punctuation, " " for space, "+" as a literal plus key, <token> aliases, and {pattern} runtime captures. It parses concatenated multi-stroke sequences such as dd, <leader>s, or {count}j.

Recognized modifier prefixes (case-insensitive, joined with +):

ModifierAliases
ctrlcontrol
shift-
metaalt, option
super-
hyper-

Field addons

ExportAdds
registerBindingOverrides()Layer-local bindingOverrides field for replacing bindings by string command name
registerEnabledFields()Layer and command enabled fields
registerMetadataFields()Binding desc / group and command desc / title / category metadata
registerAliasesField()Layer-local aliases field for remapping single-key binding names

registerBindingOverrides() expects bindingOverrides to already be a Binding[] array. It rewrites that layer’s binding array once at registration time. Matching string commands are replaced by the override binding and unmatched bindings stay in place.

registerEnabledFields() accepts boolean, () => boolean, or ReactiveMatcher values. It applies to layers and commands, not bindings. It controls activation only; it does not publish attrs.

registerMetadataFields() validates and trims metadata fields before exposing them in projections. Binding desc / group become binding metadata. Command desc / title / category become command metadata in commandAttrs.

Layer metadata is app-specific. Register layer fields directly with registerLayerFields() and call ctx.attr(...) when graph/debug visualizations need layer labels or grouping metadata.

registerAliasesField() adds bindings instead of replacing the originals and aliases stay local to the layer that declared them.

Syntax and sequence addons

ExportDescription
registerCommaBindings()Expands x, y into multiple bindings
registerEmacsBindings()Parses Emacs-style spaced chords such as ctrl+x ctrl+s
registerLeader()Defines a token such as leader that <leader> syntax expands through
registerModBindings()Adds platform-aware mod+... bindings using host metadata
registerTimedLeader()Defines a leader token and clears it after a timeout
registerBackspacePopsPendingSequence()Lets Backspace step back through the current pending sequence
registerEscapeClearsPendingSequence()Lets Escape cancel the current pending sequence
registerNeovimDisambiguation()Timeout-based exact-vs-prefix disambiguation

Options:

APIOptions
registerLeader()LeaderOptions: trigger, optional semantic name (default "leader")
registerTimedLeader()TimedLeaderOptions: LeaderOptions plus timeoutMs, onArm, onDisarm
registerBackspacePopsPendingSequence()BackspacePopsPendingSequenceOptions: preventDefault (default true), priority
registerEscapeClearsPendingSequence()EscapeClearsPendingSequenceOptions: preventDefault (default true), priority
registerNeovimDisambiguation()NeovimDisambiguationOptions: timeoutMs (default 300)

Register disambiguation addons before registering same-layer exact/prefix bindings such as g and gg.

Leader trigger accepts a key string, key object, binding-like { key }, or a single binding lookup result from createBindingLookup().get(...).

Diagnostics

ExportDescription
registerDeadBindingWarnings()Warns when a binding has no command and no reachable continuation
registerUnresolvedCommandWarnings()Warns when a string command cannot be resolved

Ex commands

registerExCommands() installs ex-command field compilers, a command transformer and a resolver for :name ...args strings. Register ex commands through normal keymap layers by using a colon-prefixed command name or namespace: "excommands".

import { registerExCommands } from "@opentui/keymap/addons"

registerExCommands(keymap)

keymap.registerLayer({
  commands: [
    {
      name: "write",
      namespace: "excommands",
      aliases: ["w"],
      nargs: "1",
      desc: "Write file",
      run({ payload }) {
        // payload.raw === ":write session.log"
        // payload.args === ["session.log"]
      },
    },
  ],
})

ExCommand fields:

FieldTypeRequiredDescription
namestringyesCommand name. :name and name both normalize to :name
aliasesstring[]noAdditional names
nargs"0", "1", "?", "*", "+"noArgument-count validation
run(ctx: CommandContext<TTarget, TEvent, ExCommandPayload>) => CommandResultyesctx.payload contains the trimmed raw input and parsed args
extra fieldsunknownnoAdded as top-level fields on the registered command
  • Registered ex commands default namespace to excommands.
  • Aliases produce additional registered commands, for example :write and :w.
  • Extra fields are preserved on getCommands() / getCommandEntries() results and compiled through command-field addons.
  • runCommand(":write file.txt") and dispatchCommand(":write file.txt") both go through the installed resolver.

OpenTUI addons

@opentui/keymap/addons/opentui re-exports every universal addon and adds the OpenTUI-specific helpers below.

registerBaseLayoutFallback()

Adds an event match resolver that falls back to KeyEvent.baseCode so bindings can ignore active keyboard layout changes when Kitty base-layout reporting is available. Direct stroke matches win before base-layout fallback matches, even across active layers.

Edit buffer helpers

ExportDescription
createTextareaBindings()Returns generated textarea bindings with overrides prepended
registerEditBufferCommands()Registers the shared edit-buffer command set against renderer.currentFocusedEditor
registerTextareaMappingSuspension()Suspends focused TextareaRenderable mapped shortcuts while keeping plain typing intact
registerManagedTextareaLayer()High-level textarea integration: commands, suspension, default bindings and overrides

registerManagedTextareaLayer() accepts a global Layer shape plus optional bindings, but it intentionally excludes target and targetMode. The helper is global and follows renderer.currentFocusedEditor.

EditBufferCommandOptions:

FieldTypeDescription
categorystringOverride generated command category, defaulting to Text Editing
groupstringOverride generated binding group, defaulting to Text Editing
includeFineGroupbooleanInclude hard-coded binding fineGroup fields for apps that register that field
commandNamesPartial<Record<EditBufferCommandName, string>>Override generated command names
descriptionsPartial<Record<EditBufferCommandName, string>>Override generated command descriptions

registerEditBufferCommands() and registerTextareaMappingSuspension() are reference-counted per keymap, so multiple integrations can safely share them.

EditBufferCommandName

ActionDefault commandDefault description
move-leftinput.move.leftCursor left
move-rightinput.move.rightCursor right
move-upinput.move.upCursor up
move-downinput.move.downCursor down
select-leftinput.select.leftSelect left
select-rightinput.select.rightSelect right
select-upinput.select.upSelect up
select-downinput.select.downSelect down
line-homeinput.line.homeLine start
line-endinput.line.endLine end
select-line-homeinput.select.line.homeSelect to line start
select-line-endinput.select.line.endSelect to line end
visual-line-homeinput.visual.line.homeVisual line start
visual-line-endinput.visual.line.endVisual line end
select-visual-line-homeinput.select.visual.line.homeSelect to visual line start
select-visual-line-endinput.select.visual.line.endSelect to visual line end
buffer-homeinput.buffer.homeBuffer start
buffer-endinput.buffer.endBuffer end
select-buffer-homeinput.select.buffer.homeSelect to buffer start
select-buffer-endinput.select.buffer.endSelect to buffer end
delete-lineinput.delete.lineDelete line
delete-to-line-endinput.delete.to.line.endDelete to line end
delete-to-line-startinput.delete.to.line.startDelete to line start
backspaceinput.backspaceDelete backward
deleteinput.deleteDelete forward
newlineinput.newlineNew line
undoinput.undoUndo
redoinput.redoRedo
word-forwardinput.word.forwardNext word
word-backwardinput.word.backwardPrevious word
select-word-forwardinput.select.word.forwardSelect next word
select-word-backwardinput.select.word.backwardSelect previous word
delete-word-forwardinput.delete.word.forwardDelete next word
delete-word-backwardinput.delete.word.backwardDelete previous word
select-allinput.select.allSelect all
submitinput.submitSubmit

The generated textarea bindings come from the shared defaultTextareaKeyBindings in @opentui/core. createTextareaBindings() prepends overrides before those defaults so custom bindings win by order.

See Custom Addons for the public addon-authoring APIs, lifecycle rules, callback contexts and extension-point quirks.