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.