Skip to content

Keybindings

Edit this page

The keybindings system maps action names to key patterns, allowing keyboard shortcuts to be customized without changing widget code.

How It Works

A Keybindings instance is a map from action names (strings) to lists of key identifiers:

1
2
3
4
5
6
7
8
use Symfony\Component\Tui\Input\Key;
use Symfony\Component\Tui\Input\Keybindings;

$kb = new Keybindings([
    'submit' => [Key::ENTER],
    'cancel' => [Key::ESCAPE, 'ctrl+c'],
    'delete_word' => ['ctrl+w', 'alt+backspace'],
]);

Key identifiers can be:

  • Constants from the Key class: Key::ENTER, Key::ESCAPE, Key::UP, Key::DOWN, Key::TAB, Key::F6, etc.
  • Modifier combinations: 'ctrl+c', 'alt+d', 'shift+enter', 'ctrl+shift+k', 'super+f1'.
  • Single characters (letters, digits and symbols): '+', '-', 'q', 'r'.

The modifiers are shift, ctrl, alt, super, hyper and meta. Terminals only report super, hyper and meta in CSI sequences (e.g. with the Kitty keyboard protocol), so key identifiers that use them never match on terminals that don't send those sequences.

meta is the Meta modifier of the Kitty keyboard protocol, not the key that terminals and readline call "Meta". Bind that key with alt (see the macOS Option key).

Key identifiers are validated when creating the Keybindings instance, so typos such as 'ctlr+x', 'escpe' or 'ctrl+pageup' (instead of 'ctrl+page_up') throw an exception instead of silently never matching.

8.2

The super, hyper and meta modifiers, the Key::super() method and the validation of the key identifiers were introduced in Symfony 8.2.

Always use these key identifiers instead of raw escape sequences ("\x03", "\x1b[A", etc.). The key abstraction handles differences between terminal encodings (legacy and Kitty protocol) transparently. The same identifiers work everywhere: keybindings and InputEvent listeners.

Widget Default Keybindings

Every focusable widget defines its own default keybindings via getDefaultKeybindings(). For example, InputWidget maps submit to Enter, select_cancel to Escape, cursor_left to Left arrow, etc.

You can see a widget's defaults in its documentation page under "Default Keybindings."

Overriding Widget Keybindings

Pass a Keybindings instance to the widget constructor to override specific actions:

1
2
3
4
5
6
7
8
use Symfony\Component\Tui\Input\Key;
use Symfony\Component\Tui\Input\Keybindings;
use Symfony\Component\Tui\Widget\EditorWidget;

$editor = new EditorWidget(keybindings: new Keybindings([
    'submit' => ['ctrl+s'],
    'new_line' => [Key::ENTER],
]));

Only the actions you specify are overridden; the remaining actions keep their defaults.

The Three-Layer Merge

When a widget checks its keybindings, three layers are merged (later layers override earlier ones):

  1. Widget defaults: the built-in bindings defined by the widget class.
  2. Tui-level keybindings: bindings passed to the Tui constructor, shared across all widgets.
  3. Widget-level keybindings: bindings set on the widget instance via setKeybindings() or the constructor.

This lets you set application-wide overrides at the Tui level while still allowing per-widget customization:

1
2
3
4
5
6
7
use Symfony\Component\Tui\Input\Keybindings;
use Symfony\Component\Tui\Tui;

// Application-wide: remap submit to Ctrl+S for all widgets
$tui = new Tui(keybindings: new Keybindings([
    'submit' => ['ctrl+s'],
]));

Display Labels

The KeyBindingWidget displays the keybindings of the focused widget. It only displays the first key of each action, so put the main shortcut first when you override a binding. Use the setKeybindingLabels() method of the widgets to choose which actions are displayed and with which labels (see how to change the displayed bindings).

8.2

The setKeybindingLabels() method was introduced in Symfony 8.2.

Action Names

Action names use snake_case by convention. Common action names shared across widgets:

Action Meaning
submit Confirm / send
select_cancel Cancel / dismiss
cursor_up Move cursor up
cursor_down Move cursor down
cursor_left Move cursor left
cursor_right Move cursor right
cursor_word_left Move cursor one word left
cursor_word_right Move cursor one word right
cursor_line_start Move cursor to line start
cursor_line_end Move cursor to line end
delete_char_backward Delete character before cursor
delete_char_forward Delete character after cursor
delete_word_backward Delete word before cursor
select_up Move selection up
select_down Move selection down
select_confirm Confirm selection
select_page_up Page up in a list
select_page_down Page down in a list
choice_toggle Toggle the highlighted item in a list

macOS: Option Key as Alt/Meta

On macOS, the Option key acts as a compose key by default (for typing accented characters like e, n). Terminal applications that rely on Alt key combinations, such as alt+backspace (delete word), alt+d (delete word forward), alt+b/alt+f (word navigation), require the Option key to be configured as Meta/Alt in your terminal emulator:

Terminal Setting
Ghostty macos-option-as-alt = true in ~/.config/ghostty/config
iTerm2 Preferences > Profiles > Keys > Left Option Key > Esc+
Terminal.app Preferences > Profiles > Keyboard > Use Option as Meta key
Kitty macos_option_as_alt yes in kitty.conf

Without this setting, pressing Option+Backspace is handled by macOS itself and never reaches the application.

With these settings, the Option key sends Alt key combinations, even when the terminal calls it "Meta": bind Option+X as 'alt+x', not 'meta+x'.

This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
TOC
    Version