Keymap
About 1811 wordsAbout 6 min
2026-07-31
Every binding in the client, by context. For the model behind it, see From the keyboard.
Keys are offered to contexts in a fixed order and the first claimant stops propagation, so a binding listed under a lower layer only fires when nothing above it claimed the key.
Global
Reachable from wherever no pane or overlay claimed the key first.
| Key | Action |
|---|---|
| ctrl+c | Quit. Checked above everything else, so it works from every screen including modals, consent and login. |
| ctrl+p | Toggle the command palette. Not reachable from a modal, consent, or the login screens. |
| ? | Open the help overlay. Types a literal ? in any text field, since those claim character keys first. |
| / | Open in-chat search. Only from selection mode, and only with a chat open. |
| Esc | Pop one level. See below. |
Only ctrl+p is configurable (keys.palette). ? and ctrl+c are hard-coded despite living beside it in the code.
What Esc pops, in order
- The newest visible toast, if any
- Out of the archive, if the chat list is showing it
- Selection mode, the chat filter (discarding it), the palette, or in-chat search (clearing highlights), depending on what's on top
- Anything else on the stack
- From the composer at the base of the stack: back to the chat list
- At the very bottom: nothing. Esc never quits.
Esc is intercepted centrally, above the pane handlers, which is what makes "one Esc, one level" hold everywhere.
Pane movement
Only at the base of the focus stack (never with an overlay, filter, selection or modal up).
| Key | From | To |
|---|---|---|
| → | Chat list | Composer (requires an open chat) |
| Tab / Shift+Tab | Chat list | Composer (requires an open chat) |
| Tab / Shift+Tab | Composer | Chat list |
← is deliberately not a pane key. With two panes, Tab and Shift+Tab are indistinguishable.
Chat list
| Key | Action |
|---|---|
| ↑ / ↓ | Move the selection, clamped, no wraparound |
| Enter | Open the selected chat and move focus to the composer |
| / | Start the title filter |
| a | Toggle the archive |
| ] | Next folder, wrapping. No-op in the archive or with one folder. |
| [ | Previous folder, same conditions |
| Esc | Leave the archive (only while in it) |
Chat filter
| Key | Action |
|---|---|
| any character | Insert into the query |
| Backspace | Delete backwards |
| ↑ / ↓ | Move the selection within the filtered list |
| Enter | Commit: close the input, keep the list filtered |
| Esc | Cancel and clear the filter |
No caret movement. ←, →, Home, End and Delete are inert here.
Composer
| Key | Action |
|---|---|
| any character | Insert at the caret |
| Enter | Send, or apply an edit if one is armed, or confirm a /send |
| Alt+Enter | Insert a newline |
| ↑ on empty input | Enter selection mode on the newest message |
| ↑ with text | Move the caret up a line |
| ← / → | Move the caret one character |
| Home / End | Start / end of the buffer |
| Backspace / Delete | Delete before / after the caret |
| ↓, PageUp, PageDown | Not claimed: scroll the conversation |
/send <path> followed by Enter raises the send-file confirmation. Pasting or dropping a bare path does the same without typing /send: bracketed paste is on, and text that looks like a single-line path is held as a pending offer instead of inserted. Enter confirms it the same way, Esc discards it and returns to an empty composer. Anything else pasted arrives as ordinary text at the caret.
Conversation scrolling
There is no separate conversation focus. These work from the composer and from selection mode.
| Key | Action | Available from |
|---|---|---|
| ↓ | One message newer | Composer only (selection mode claims it) |
| PageUp | A page older | Composer and selection mode |
| PageDown | A page newer | Composer and selection mode |
Scrolling near the top of the loaded window triggers a history page. There is no row-level scrolling; movement is per message.
Selection mode
| Key | Action |
|---|---|
| ↑ / ↓ | Select an older / newer message, clamped |
| ← / → | Move the chip cursor, clamped. Up to five chips visible. |
| Enter | Invoke the focused chip |
| Esc | Leave selection mode |
| chip letter | Invoke that chip directly |
Chip letters
| Key | Chip | Shown when |
|---|---|---|
| r | Reply | Always |
| f | Forward | The message can be forwarded |
| e | React | Always. Toggles 👍. |
| c | Copy | The message can be saved |
| d | Edit | Your own message, editable, and text content only |
| x | Delete | Deletable for you or for everyone |
| l | Download | Has a file that isn't downloaded |
| o | Open | Has a file that is downloaded |
| s | Resend | The send failed. Failed messages show only Resend, Delete, and Cancel upload (if a file upload is still tracked for it). |
| v | Reveal | The message has an unrevealed spoiler. Never shown on a failed send: nothing server-confirmed to reveal. |
| k | Cancel upload | A file you sent from this message is still uploading. Shown even after a failed send, unlike Reveal, since an upload stuck mid-transfer is exactly what you'd want to abandon. |
A letter with no matching chip in the current row isn't swallowed; it falls through to the global layer, which is how ? and / still work here.
In-chat search
Entered with / from selection mode.
| Key | Action |
|---|---|
| any character | Insert into the query |
| Backspace | Delete backwards |
| Enter | Run the search |
| n | Next hit, wrapping. Only once hits exist; before that it types an n. |
| N | Previous hit, wrapping. Same condition. |
| Esc | Close search and clear the highlights |
Hits come back newest-first, so n walks toward older messages. Arrow keys, Home, End and Delete are inert.
Command palette
| Key | Action |
|---|---|
| any character | Insert into the query and re-rank |
| Backspace | Delete backwards |
| ↑ / ↓ | Move the selection, clamped |
| Enter | Run the selected item |
| ctrl+p / Esc | Close |
No caret movement in the query. Commands: Toggle theme, Log out, Quit, plus Telemetry settings and Send file, which are both claimed no-ops.
Modals
Modals swallow every key except ctrl+c. Neither the palette nor help is reachable while one is up.
Confirm delete. Enter deletes, Esc cancels. Arrow keys and Tab toggle between "Delete for me" and "Delete for everyone", but only when the message can be deleted for everyone; otherwise the choice is forced.
Confirm send file. Enter sends, Esc cancels. No cursor, no options.
Help overlay
Opened with ?, closed with Esc. It swallows every key while up, including a second ?.
There is no scrolling. If the keymap doesn't fit the terminal height, the last visible row becomes a dim …, which happens at 80×24. The overlay's own table is also incomplete relative to this page: it omits the filter, palette and search bindings, the conversation scroll keys, and several Esc cases, and it lists ←/→ as pane movement, which ← has never been.
Consent screen
| Key | Action |
|---|---|
| ↑ ↓ ← → Tab Shift+Tab | Flip between Enable and Disable |
| Enter | Record the answer and continue |
Enable is preselected. Every other key is swallowed.
Login screens
Method choice
| Key | Action |
|---|---|
| ↑ / ↓ | Flip between phone and QR |
| p / q | Pick phone / QR directly |
| Enter | Confirm. Phone opens the number field; QR fires the request. |
With QR picked but not yet confirmed, ↑, ↓ and p flip back to phone and Enter fires the request.
Credentials wizard
| Key | Action |
|---|---|
| Tab / ↓ | API id to API hash |
| Shift+Tab / ↑ | API hash to API id |
| Enter on API id | Move to API hash (does not submit) |
| Enter on API hash | Submit and save |
Text fields (phone, code, password, credentials)
| Key | Action |
|---|---|
| any character | Insert at the caret |
| Enter | Submit |
| ← / → | Move the caret |
| Home / End | Start / end |
| Backspace / Delete | Delete before / after |
Submission is blocked while a request is in flight or during a flood wait. The QR display screen and the intermediate phases claim every key and do nothing; ctrl+c is the only way out of them. Esc never reaches the escape handler while a login screen is up.
Mouse
Requires [app] mouse = true (the default).
| Gesture | Target | Action |
|---|---|---|
| Left click | Chat row | Select and open |
| Left click | Archive row | Enter the archive |
| Left click | Folder tab | Switch folder, select its first row |
| Left click | Composer | Focus the composer (needs an open chat) |
| Left click | Spoiler run in a message | Reveal it |
| Left click | Reply-quote line in a message | Jump to the quoted message |
| Right click | Message | Enter selection mode on it |
| Wheel | Chat list | Move the selection one row per step |
| Wheel | Conversation | Scroll one message per step |
Left-clicking ordinary message text is still a no-op. Only a masked spoiler run or a reply-quote line responds, and both are narrower hit targets than the message's full row. Hover, drag, release, the middle button and horizontal scroll are all discarded. Nothing is clickable while a modal, the palette or help is up, or on the consent and login screens.
Not bound to anything
| What | Status |
|---|---|
| Function keys, Insert, media keys | Dropped at the input layer |
| Alt+ anything except Enter | Arrives as the bare key; there is no Alt modifier in the model |
| Rebinding ? or ctrl+c | Not configurable |
