Custom keybindings
This page lists every rebindable action in gitoui, global UserEvents first, then each per-view scope, with the default keys, what each action does, and how to override them.
How resolution works
Section titled “How resolution works”Two layers : the global map covers everything ([keybind] in TOML),
scopes override the global map inside a specific view
([keybind.scope.<path>]).
When you press a key in a given view, gitoui walks this chain:
- Leaf scope, the deepest scope the view exposes (e.g.
pr.conversation,rebase.resume). If the key is bound here, fire the action. - Parent scopes, walk up one level at a time. From
pr.conversation→pr. Fromrebase.resume→rebase. - Global, the
[keybind]section. - Literal forward, neither matched → the view gets the raw key event for its own char-level matches (rare).
The key fires the first action it matches. Once a leaf binds a key, parents are skipped.
Inheritance, sub-views see their parent’s bindings
Section titled “Inheritance, sub-views see their parent’s bindings”A sub-view automatically inherits every key its parent scope defines. You only need to list a binding in the leaf scope if you want it to override the parent.
For example, in the PR view:
risreload(defined in[scope.pr]).- The Conversation tab inherits that, pressing
rthere still reloads the PR. - But
ris also bindable inside[scope.pr.conversation]. If you set it there, the conversation-level binding wins only on that tab. - Once you leave the tab,
rfalls back to the PR-widereload.
The same chain runs in: pr.list → pr → global, issues.detail →
issues → global, rebase.resume → rebase → global, etc.
Override vs conflict
Section titled “Override vs conflict”By contrast, the same key across different scopes is fine, that’s the whole point of scopes:
[keybind]stage = ["a"] # global: `a` stages a file in Uncommitted
[keybind.scope.pr]approve = ["a"] # in PR: `a` approves, overrides the global `stage`
[keybind.scope.issues]assignees_picker = ["a"] # in Issues: `a` opens the assignees pickerWhen you’re in the PR view, a fires approve (and the global stage
is silent). The global stage only fires in views without a scoped
binding on a.
Merge semantics, overrides REPLACE, not extend
Section titled “Merge semantics, overrides REPLACE, not extend”If your config sets approve = ["ctrl-a"], the default a → approve
binding is dropped, only Ctrl+A triggers approve. To keep both, list
them both explicitly:
[keybind.scope.pr]approve = ["a", "ctrl-a"] # both keys fire approveAnything you don’t touch keeps its full default key list.
Modifier cap
Section titled “Modifier cap”gitoui accepts at most one modifier prefix per key
ctrl-X alt-X shift-X.
Chained combos like ctrl-shift-a are rejected at config-load.
Key syntax
Section titled “Key syntax”Each key is a string. Format: [modifier-]base. Examples: q, ctrl-s,
shift-h, f5, enter, esc, space.
| Base key | Accepted spellings |
|---|---|
| Letters | a, b, … z (case-insensitive, shift-a and A are the same event). |
| Digits | 0–9 |
| Function | f1 … f12 |
| Arrows | up, down, left, right |
| Whitespace | space, tab, backtab |
| Editing | enter, esc, backspace, delete, insert |
| Page nav | home, end, pageup, pagedown |
| Punctuation | The character itself: ], +, #, =, - |
| Modifiers | ctrl-, alt-, shift- (max one prefix). |
Empty array unbinds a default key entirely:
[keybind]go_to_parent = [] # no key fires GoToParentGlobal, [keybind]
Section titled “Global, [keybind]”Available from every view. Use the action name on the left, give it any list of keys.
App-wide
Section titled “App-wide”| Action | Default | What it does |
|---|---|---|
force_quit | ctrl-c | Hard-quit, bypass any confirmation. |
quit | q | Quit from the list / close the active view. |
help_toggle | ? f1 | Open / close the in-app Help page. |
cancel | esc | Cancel current action, close overlays, back out. |
close | (unbound) | Alternate “close view” hook for custom flows. |
config | p | Open the Configuration view. |
pull_requests | shift-r | Open the PR list (needs GitHub auth). |
issues | shift-i | Open the Issues list (needs GitHub auth). |
Navigation
Section titled “Navigation”| Action | Default | What it does |
|---|---|---|
navigate_up | k up | Move cursor up. |
navigate_down | j down | Move cursor down. |
navigate_left | h left | Move cursor / focus left. |
navigate_right | l right | Move cursor / focus right. |
go_to_parent | alt-j alt-down | Drill into the parent commit / branch. |
select_up | shift-k | Extend selection up. |
select_down | shift-j | Extend selection down. |
go_to_top | g | Jump to the top of the list. |
go_to_bottom | shift-g | Jump to the bottom. |
scroll_up | ctrl-y | Scroll one line up (cursor stays). |
scroll_down | ctrl-e | Scroll one line down. |
page_up | ctrl-b pageup | Page up. |
page_down | ctrl-f pagedown | Page down. |
half_page_up | ctrl-u | Half-page up. |
half_page_down | ctrl-d | Half-page down. |
select_top | (unbound) | Position cursor at the top of the viewport. |
select_middle | shift-m | Position cursor at the middle of the viewport. |
select_bottom | shift-l | Position cursor at the bottom of the viewport. |
confirm | enter | Drill into the focused item. |
go_to_next | n | Jump to the next search match. |
go_to_previous | shift-n | Jump to the previous search match. |
ref_list | tab | Toggle the refs panel from the list view. |
Search
Section titled “Search”| Action | Default | What it does |
|---|---|---|
search | f | Open the search bar in the list / diff view. |
ignore_case_toggle | s | Toggle case-insensitive search in active mode. |
fuzzy_toggle | z | Toggle fuzzy search. |
Repo-level actions
Section titled “Repo-level actions”| Action | Default | What it does |
|---|---|---|
refresh | r | Reload the view’s state from disk / API. |
mark_compare | space | Mark an endpoint for the 2-commit comparison flow. |
push | shift-p | git push (current branch). |
pull | shift-u | git pull, also doubles as unstage_all / pull_branch in their respective views. |
short_copy | shift-c | Copy short hash / short ref. |
full_copy | c | Copy full hash / commit message / path. |
Commit-detail actions
Section titled “Commit-detail actions”| Action | Default | What it does |
|---|---|---|
add_tag | t | Add a tag on the focused commit. |
create_branch | shift-b | Create a new branch starting at the focused commit. |
blame | b | Open blame for the focused file. |
checkout | o | Checkout the focused commit / branch. |
cherry_pick | shift-o | Cherry-pick the focused commit. |
drop | d | Drop the focused commit (interactive rebase). |
merge | m | Merge the focused branch into current. |
rebase | e | Open the interactive rebase plan editor. |
reset | shift-s | git reset to the focused commit (mixed by default). |
squash | ctrl-s | Squash the focused commit into its parent. |
amend_commit | ctrl-m | Amend HEAD’s commit message (HEAD only). |
file_history | shift-h | git log --follow on the focused file. |
abort_operation | ctrl-a | Abort an in-progress rebase / merge / cherry-pick. |
Branch + tag actions
Section titled “Branch + tag actions”| Action | Default | What it does |
|---|---|---|
rename_branch | ctrl-r | Rename the focused branch. |
delete_branch | shift-d | Delete the focused branch. |
push_branch | shift-q | Push the focused branch. |
pull_branch | (unbound) | Pull the focused branch, aliased on shift-u via the pull action in the BranchDetail view. |
set_upstream | ctrl-t | Set / change the upstream for the focused branch. |
create_archive | shift-e | git archive the focused branch / tag. |
unselect_branch | shift-t | Clear the branch-detail selection. |
copy_branch_name | shift-v | Copy the focused branch name. |
delete_tag | shift-f | Delete the focused tag. |
push_tag | shift-w | Push the focused tag. |
copy_tag_name | shift-y | Copy the focused tag name. |
Uncommitted (staging)
Section titled “Uncommitted (staging)”| Action | Default | What it does |
|---|---|---|
stage | a | Stage the focused file (or open the conflict editor if the file is unmerged). |
stage_all | shift-a | Stage every unstaged + untracked file. |
unstage | u | Unstage the focused file. |
unstage_all | (unbound) | Unstage every staged file, aliased on shift-u via pull in the Uncommitted view. |
discard | x | Discard the focused unstaged change (confirmation). |
discard_all | shift-x | Discard every unstaged + staged change (confirmation). |
stash | i | Stash the working tree. |
commit | w | Open the commit message editor. |
clean_untracked | v | Clean (delete) the focused untracked file. |
Stash actions
Section titled “Stash actions”| Action | Default | What it does |
|---|---|---|
apply_stash | y | Apply the focused stash. |
pop_stash | ctrl-p | Pop the focused stash. |
drop_stash | ctrl-x | Drop the focused stash. |
create_branch_from_stash | ctrl-n | Create a new branch from the stash. |
copy_stash_name | ctrl-i | Copy the stash name. |
copy_stash_hash | ctrl-o | Copy the stash hash. |
Diff view
Section titled “Diff view”| Action | Default | What it does |
|---|---|---|
cycle_file_next | + | Jump to the next file in the current diff. |
cycle_file_prev | - | Jump to the previous file. |
User commands
Section titled “User commands”User-defined shell commands (configured under [core.user_command])
bind through synthetic actions named user_command_N, where N matches
the commands_N entry. None ship with a default key, every binding is
user-set.
[keybind]user_command_1 = ["f5"]user_command_2 = ["f6"]Full setup, placeholders and execution types live on the User commands page.
Scoped, [keybind.scope.<path>]
Section titled “Scoped, [keybind.scope.<path>]”Each scope below ships with its own default key map. Anything you don’t list there keeps its parent / global default, you only need to write the action you want to change.
[keybind.scope.list], Commit list
Section titled “[keybind.scope.list], Commit list”Inherits from: [keybind] (global).
| Action | Default | What it does |
|---|---|---|
select_head_commit | h | Scroll to and center the HEAD commit in the viewport. |
h is navigate_left globally; in the list view it has no left/right
semantics so the scope reuses it for “jump to HEAD”.
[keybind.scope.detail], Commit detail
Section titled “[keybind.scope.detail], Commit detail”Inherits from: [keybind] (global).
| Action | Default | What it does |
|---|---|---|
revert | v | Open the Revert confirmation dialog for the focused commit. |
v globally is clean_untracked (uncommitted view). In commit-detail
there’s nothing to clean, so the scope re-purposes v for revert
matches the action shown in the Git Actions panel (Revert (v)).
[keybind.scope.pr], Pull Request
Section titled “[keybind.scope.pr], Pull Request”Inherits from: [keybind] (global). Applies on any tab inside an
open PR.
| Action | Default | What it does |
|---|---|---|
reload | r | Reload PR data from the GitHub API. |
approve | a | Submit an approving review. |
request_changes | x | Submit a changes-requested review. |
merge | m | Open the merge dialog. |
open_in_browser | o | Open the PR’s URL via xdg-open / open. |
labels_picker | l | Open the labels picker. |
reviewers_picker | v | Open the reviewers picker. |
close_pr | ctrl-x | Close the PR (confirmation). |
reopen_pr | ctrl-o | Reopen a closed PR. |
toggle_draft | ctrl-d | Toggle ready ↔ draft state. |
[keybind.scope.pr.list], PR list
Section titled “[keybind.scope.pr.list], PR list”Inherits from: [keybind.scope.pr] → [keybind].
| Action | Default | What it does |
|---|---|---|
new_pr | n | Open the new-PR compose form. |
[keybind.scope.pr.conversation], PR Conversation tab
Section titled “[keybind.scope.pr.conversation], PR Conversation tab”Inherits from: [keybind.scope.pr] → [keybind].
| Action | Default | What it does |
|---|---|---|
new_comment | c | Open the comment editor overlay. |
quote_reply | shift-r | Quote-reply to the focused comment. |
edit_own | e | Edit your own focused comment. |
delete_own | d | Delete your own focused comment (confirmation). |
react | + | Open the emoji picker on the focused comment. |
[keybind.scope.issues], Issue
Section titled “[keybind.scope.issues], Issue”Inherits from: [keybind] (global). Applies on any tab of an
open issue.
| Action | Default | What it does |
|---|---|---|
reload | r | Reload issue data from the GitHub API. |
labels_picker | l | Open the labels picker. |
assignees_picker | a | Open the assignees picker. |
milestone_picker | m | Open the milestone picker. |
close_or_reopen | x | Close an open issue / reopen a closed one (confirmation). |
open_in_browser | o | Open the issue’s URL in the browser. |
[keybind.scope.issues.list], Issues list
Section titled “[keybind.scope.issues.list], Issues list”Inherits from: [keybind.scope.issues] → [keybind].
| Action | Default | What it does |
|---|---|---|
new_issue | n | Open the new-issue compose form. |
[keybind.scope.issues.detail], Issue Conversation / Timeline
Section titled “[keybind.scope.issues.detail], Issue Conversation / Timeline”Inherits from: [keybind.scope.issues] → [keybind].
| Action | Default | What it does |
|---|---|---|
new_comment | c | Open the comment editor overlay. |
quote_reply | shift-r | Quote-reply to the focused comment. |
reference_in_new_issue | shift-n | Open Compose pre-filled with a back-reference link. |
edit_own | e | Edit your own focused comment. |
delete_own | d | Delete your own focused comment (confirmation). |
react | + | Open the emoji picker on the focused comment. |
[keybind.scope.rebase], Interactive rebase plan editor
Section titled “[keybind.scope.rebase], Interactive rebase plan editor”Inherits from: [keybind] (global).
| Action | Default | What it does |
|---|---|---|
pick | p | Pick, keep the focused commit. |
reword | r | Reword, keep but open the inline subject editor. |
edit | e | Edit, pause the rebase at this commit. |
squash | s | Squash into the previous commit, keep both messages. |
fixup | f | Fixup, combine + drop this message. |
drop | d | Drop, remove the commit. |
grab | space | Grab the focused row to reorder it with ↑↓. |
abort_previous | shift-a | Abort a stale rebase that’s still active on disk. |
[keybind.scope.rebase.resume], Resume prompt
Section titled “[keybind.scope.rebase.resume], Resume prompt”Inherits from: [keybind.scope.rebase] → [keybind]. Shown when a
rebase is paused.
| Action | Default | What it does |
|---|---|---|
continue_rebase | c shift-c | git rebase --continue. |
skip_commit | s shift-s | git rebase --skip. |
abort | a shift-a | git rebase --abort. |
[keybind.scope.rebase.reword_editor], Reword inline editor
Section titled “[keybind.scope.rebase.reword_editor], Reword inline editor”Inherits from: [keybind.scope.rebase] → [keybind]. Word-jump keys
inside the inline subject editor.
| Action | Default | What it does |
|---|---|---|
delete_word_left | ctrl-h ctrl-w ctrl-backspace | Delete one word to the left. |
delete_word_right | ctrl-delete | Delete one word to the right. |
word_left | ctrl-left | Jump cursor one word left. |
word_right | ctrl-right | Jump cursor one word right. |
[keybind.scope.conflict], 3-way conflict editor
Section titled “[keybind.scope.conflict], 3-way conflict editor”Inherits from: [keybind] (global).
| Action | Default | What it does |
|---|---|---|
pick_ours | o | Pick OURS for the focused hunk. |
pick_theirs | t | Pick THEIRS. |
pick_both_ours_first | b | Keep BOTH, OURS lines first. |
pick_both_theirs_first | shift-b | Keep BOTH, THEIRS lines first. |
next_hunk | n | Move to the next conflict hunk. |
prev_hunk | p | Move to the previous conflict hunk. |
[keybind.scope.compose], Compose forms
Section titled “[keybind.scope.compose], Compose forms”Inherits from: [keybind] (global). Shared by new PR / new Issue /
new comment / reply editors.
| Action | Default | What it does |
|---|---|---|
submit | ctrl-s | Submit the compose form to the GitHub API. |
The mention popup (#), word-jump editing keys, and Esc to cancel stay
hardcoded, they’re shared text-input semantics, not view actions.
Worked examples
Section titled “Worked examples”Keep h/j/k/l pure for navigation, move scrolling off the home row.
[keybind]half_page_up = ["ctrl-u"]half_page_down = ["ctrl-d"]
[keybind.scope.list]# Free `h` if you don't want the HEAD-jump action. An empty array drops# it, the global `navigate_left` (still `h`) wins.select_head_commit = []Map daily actions to F-keys for IDE-like muscle memory.
[keybind]refresh = ["f5"]search = ["f4"]help_toggle = ["f1"]config = ["f2"]
[keybind.scope.pr]approve = ["f6"]request_changes = ["f7"]merge = ["f8"]reload = ["f5"]Mirror GitHub web shortcuts for the muscle-memory swap.
[keybind.scope.pr.conversation]new_comment = ["c"] # same as webreact = ["r"] # web uses `:` but we keep rquote_reply = ["shift-r"]edit_own = ["ctrl-e"]delete_own = ["ctrl-d"]Put a custom key on a sub-view action while keeping the parent’s binding untouched.
# In PR Conversation, `r` is `reload` (inherited from scope.pr) by# default. Override it locally to be `react` while still letting the# rest of the PR view reload with `r`.[keybind.scope.pr.conversation]react = ["r", "+"]The parent [scope.pr] reload = ["r"] still wires r to reload on
the Files / Commits / References tabs.
Pass an empty array to drop a default key entirely:
[keybind]go_to_parent = [] # no key fires GoToParent
[keybind.scope.pr.conversation]react = [] # `+` no longer opens the reaction pickerWhat happens on a bad config
Section titled “What happens on a bad config”gitoui validates the full keybind tree at boot and refuses to start on:
- Duplicate key in the same scope,
abound to two different actions under the same[keybind.scope.<path>]. - Unknown action name, typo in a TOML key.
- Unknown key spelling, e.g.
superkey-a. - More than one modifier,
ctrl-shift-a.
The diagnostic block points at the offending line, column, scope, and expected schema, same style as the TOML-syntax + theme validators.