Skip to content

Configuration reference

Every option in config.toml, with its type and default. Set only what you want to change, since anything left out keeps its default.

This page lists facts. To create the file and apply changes, see Configuring Neru. For how bindings combine, see How bindings work. For ready-made configs, see Recipes.

The syntax and defaults of every hotkey table. When each table applies, and which one answers a key, is in How bindings work.

[hotkeys]
"Primary+Shift+Space" = "hints"
"Primary+Shift+W" = ["action save_cursor_pos", "hints --action left_click"]

The key is a chord, "Mod1+Mod2+Key". The value is one step or an array of steps.

Defaults. macOS and Windows bind Primary+Shift+Space (hints), Primary+Shift+G (grid), Primary+Shift+C (recursive grid), Primary+Shift+B (bisect) and Primary+Shift+S (scroll). Linux binds none, see Getting started.

ModifierAliases
CmdCommand, Super, Meta
CtrlControl
AltOption
Shift
PrimaryCmd on macOS, Ctrl on Linux/Windows
CategoryKeys
Lettersa to z, A to Z
Numbers0 to 9
Symbols`, -, =, [, ], \, ;, ', ,, ., /
NamedSpace, Return, Enter, Escape, Tab, Delete, Backspace
NavigationUp, Down, Left, Right, Home, End, PageUp, PageDown, Insert (Linux and Windows only)
FunctionF1 to F24 (F21 to F24 on Linux and Windows only)
  • Shift with a symbol is written as the character Shift produces. On Linux, Shift plus the ; key is "Shift+:", not "Shift+;". Letters are unaffected.
  • Delete and Backspace both name the key that erases to the left. The forward-delete key has no hotkey name.
  • The full key list with platform behavior is under neru action feed.
ConfigResult
Section absentAll defaults used
Section present, emptyAll hotkeys disabled
Section has entriesMerged on top of defaults

Use __disabled__ to remove one default:

[hotkeys]
"Primary+Shift+S" = "__disabled__" # removes default scroll binding
"Ctrl+Space" = "hints" # adds binding, other defaults unchanged

Disabling a mode (enabled = false) also removes its default launcher hotkey.

[<mode>.hotkeys] follows the same merging rules, and also accepts multi-key sequences such as gg. Every built-in mode except monitor_select ships these, plus the defaults listed in its own section:

"Escape" = "idle"
"Shift+L" = "action left_click"
"Shift+R" = "action right_click"
"Shift+M" = "action middle_click"
"Shift+I" = "action left_click --state down"
"Shift+U" = "action left_click --state up"
"Up" = "action move_mouse_relative --dx=0 --dy=-10"
"Down" = "action move_mouse_relative --dx=0 --dy=10"
"Left" = "action move_mouse_relative --dx=-10 --dy=0"
"Right" = "action move_mouse_relative --dx=10 --dy=0"

[[<mode>.app_configs]] overrides [<mode>.hotkeys] for hints, grid, recursive_grid, bisect, scroll and declared modes ([[modes.<name>.app_configs]]), under the same merging rules.

[[hints.app_configs]]
bundle_id = "com.brave.Browser"
hotkeys = { "Return" = "action left_click", "Shift+L" = "__disabled__" }

A root-level [[app_configs]] entry overrides [hotkeys] for one app:

[[app_configs]]
bundle_id = "com.apple.Terminal"
hotkeys = { "Cmd+Space" = "hints", "Cmd+Shift+Space" = "__disabled__" }

bundle_id selects the app for [[app_configs]], every [[<mode>.app_configs]] and excluded_apps. What it matches depends on the platform:

PlatformIdentity Neru matchesHow to find it
macOSBundle ID, reverse-DNS (e.g. com.apple.Safari)osascript -e 'id of app "Safari"'
Linux · X11Window WM_CLASS, the class fieldxprop WM_CLASS, then click the window
Linux · WaylandToplevel app_idswaymsg -t get_tree (Sway), hyprctl activewindow (Hyprland), niri msg windows (niri), or your compositor’s window inspector
WindowsFull path of the focused window’s executable (e.g. C:\Program Files\Google\Chrome\Application\chrome.exe)Task Manager, Details tab, right-click the process, Open file location, or (Get-Process chrome).Path in PowerShell

Matching is case-insensitive and exact, with no globbing or partial matches.

  • Linux identity strings vary by toolkit (e.g. Google-chrome, code, org.kde.konsole). Confirm with the commands above.
  • On Windows, write the path as a TOML literal string ('C:\...') or double every backslash. A copy installed elsewhere, such as under %LOCALAPPDATA%, needs its own entry. Microsoft Store apps all share the identity ApplicationFrameHost.exe and cannot be told apart.
  • On GNOME under Wayland, no entry matches until the Neru GNOME Shell extension loads after your first re-login. See GNOME (Wayland).

Named action sequences, invoked from any binding with macro <name> [args...]. A macro is written, not recorded.

[macros]
click_and_exit = ["action left_click --bail-on-error", "idle"]
say = ["exec say \"$1\""]
[hints.hotkeys]
"Enter" = "macro click_and_exit"
"Shift+S" = "macro say hello"

A worked example with arguments is in Recipes.

  • Names use letters, digits, _ and -, and start with a letter.
  • Arguments are positional: $1, $2, and so on, with $$ for a literal dollar sign. Substitution is textual and happens before the step is split, so quote a placeholder that may hold spaces, e.g. exec say "$1".
  • Arity is checked at load wherever an action can be written, including Mission Control hooks and nested run or --on-exit steps. An unknown name or wrong argument count fails neru config validate.
  • A placeholder cannot be the command word. "$1 --action left_click" is rejected at load.
  • A macro runs as a nested sequence, so it can call other macros, and its failure counts as one failed step to the caller.
  • A mode’s --action does not take a macro. Use --on-exit.
  • Run a macro from outside with neru macro <name> [args...].

Modes you declare yourself. A declared mode is a name, an indicator label and a hotkey table. While it is open Neru captures the keyboard and answers every key from that table, so it works as a layer of bare-letter bindings.

[modes.window]
indicator = "Window"
[modes.window.hotkeys]
"h" = "exec yabai -m window --focus west"
"f" = ["exec yabai -m window --toggle zoom-fullscreen", "idle"]
"s" = "scroll"
[hotkeys]
"Primary+Shift+W" = "mode window"
  • Entering. Use the step mode <name> from any binding, macro or run, or neru mode <name>. It accepts only --toggle. A step naming an undeclared mode is refused at load.
  • Leaving. Escape is bound to idle by default ("Escape" = "__disabled__" removes it). Any binding ending in idle or another mode also leaves. Unbound Ctrl/Alt/Cmd chords fall back to [hotkeys], see Which binding wins, and unbound bare keys are swallowed.
OptionTypeDefaultDescription
indicatorstring""Mode indicator text while the mode is open, empty hides it
hotkeysmap{ "Escape" = "idle" }The mode’s hotkeys, merged over the default

The name is the table key: letters, digits, _ and -, starting with a letter. Built-in mode names (hints, grid, recursive_grid, scroll, monitor_select, idle) and mode are refused. The indicator style comes from [mode_indicator.ui].

[[modes.<name>.app_configs]] takes bundle_id and hotkeys.

Behavior not tied to one mode.

[general]
excluded_apps = ["com.apple.Terminal"]
passthrough_unbounded_keys = true
OptionTypeDefaultDescription
excluded_appsarray[]Apps where Neru won’t activate, by app identity
kb_layout_to_usestring""Keyboard layout keys are named in, as the platform names it (auto if empty). See below
hide_overlay_in_screen_shareboolfalseHide overlay in screen sharing apps
passthrough_unbounded_keysboolfalseLet unbound Cmd/Ctrl/Alt shortcuts pass through
should_exit_after_passthroughboolfalseExit mode after a passthrough shortcut
passthrough_unbounded_keys_blacklistarray[]Shortcuts to keep consumed when passthrough is on
exec_shellstring"/bin/bash"Shell binary used for exec hotkey commands
exec_shell_argsarray["-lc"]Shell arguments, with the command string appended last

kb_layout_to_use decides which physical key a binding answers. Empty picks a layout with Latin letters, so bindings stay put while you type in another language. To force one, use your platform’s name for it:

PlatformValueExample
macOSInput source IDcom.apple.keylayout.Dvorak
LinuxXKB layout name, matched regardless of caseEnglish (Dvorak)
WindowsKeyboard layout identifier00010409

The keyboard_layouts row of neru doctor lists the layouts Neru sees, in this option’s form, and the one in use. An unmatched value is reported there and in the log, and Neru keeps its automatic choice.

passthrough_unbounded_keys and should_exit_after_passthrough work on macOS, Windows and Wayland with the evdev keyboard proxy. X11 cannot pass a grabbed chord through.

Base colors from which all component defaults are derived, set in [theme.light] and [theme.dark]. Use solid #RRGGBB or #RGB (no alpha). An explicit component color overrides the derived one.

[theme.light]
accent = "#465FBC"
[theme.dark]
accent = "#6E82D6"
KeyRoleLight defaultDark default
surfaceTranslucent fills, badges, indicator backgrounds#EEF2FF#0A1338
accentBorders, lines, primary chrome#465FBC#6E82D6
accent_altActive/emphasis states, highlights, virtual pointer#0B2377#8FA2F0
on_accent_altForeground text/icon on accent_alt surfaces#F8FAFF#081022
textForeground text on surface backgrounds#17327A#E8EEFF

Every color option takes hex with optional alpha. The alpha byte is round(opacity * 255), e.g. F2 for 95% and B3 for 70%. A color is a string, or a table with light and dark keys such as { light = "#FF0000AA", dark = "#00FF00AA" }. A color you leave out is derived from [theme] and follows the system appearance.

FormatExampleAlphaNotes
#AARRGGBB#FF000000YesRecommended format
#RRGGBB#FF0000NoFully opaque
#RGB#F00NoShorthand

Every font_family option takes a family name, or one of the generic aliases sans, serif and mono, which resolve to each platform’s own faces. Empty means sans. On Linux and Windows, a family the system cannot find falls back to DejaVu Sans or Segoe UI.

Labels clickable elements. The default axtree strategy reads the platform accessibility tree. Two screen-capture strategies cover apps with a thin tree. Both scan the focused window by default and add the system surfaces the include_* options ask for.

  • vision: one OCR pass per activation, so hint search (--search) and --split-word work. What it finds on each platform is in Platform support.
  • contour: edge analysis from wl-kbptr, a few milliseconds with no dependency. Its hints carry no text, so search and word splitting do not apply. Tune it under [hints.contour].

Press / in hints mode to filter hints by text. See the hints search recipe.

[hints]
hint_characters = "asdfghjkl"
strategy = "vision"
[hints.ui]
font_size = 12
OptionTypeDefaultDescription
enabledbooltrueTurn hints mode on or off
strategystring"axtree"Element detection: "axtree", "vision" or "contour". Overridable per app
capture_scopestring"window"Region vision and contour scan: "window" (the screen if nothing is focused) or "screen". Overridable per app and with neru hints --capture-scope
hint_charactersstring"asdfghjkl"Characters used for labels
label_directionstring"normal""normal" or "reverse", see Choosing a label direction. Overridable per app and with neru hints --label-direction
max_depthint50Deepest accessibility tree level to read, 0 for unlimited
include_menubar_hintsboolfalseShow hints on menubar items
include_dock_hintsboolfalseShow hints on Dock items
include_nc_hintsboolfalseShow hints in Notification Center
include_stage_manager_hintsboolfalseShow hints in Stage Manager
include_pip_hintsboolfalseShow hints on Picture in Picture controls
include_screen_capture_hintsboolfalseShow hints on Screen Capture controls
detect_mission_controlboolfalseEnable Mission Control state detection
on_mission_control_activatedstring/arraynoneAction(s) to execute when Mission Control opens
on_mission_control_deactivatedstring/arraynoneAction(s) to execute when Mission Control closes
additional_menubar_hints_targetsarraymacOS-specific defaultsExtra menubar bundle IDs
clickable_rolesarrayshared semantic defaultsRoles that generate hints. See Clickable roles
ignore_clickable_checkboolfalseSkip clickability heuristic
visible_check_enabledboolfalseEnable visibility hit-test (slower but fewer noisy hints)

hints.clickable_roles decides which accessibility elements get a hint. Write entries in Neru’s semantic vocabulary, and Neru resolves them to the platform’s AX roles, AT-SPI role names or UI Automation control types.

[hints]
clickable_roles = ["button", "link", "text_field"]

neru roles shows the vocabulary and how each name resolves on this machine. neru roles --explain shows how your config resolves.

SemanticmacOS (ax:)Linux (atspi:)Windows (uia:)
buttonAXButtonpush button, button, toggle buttonButton, SplitButton
menu_buttonAXMenuButtonpush button menu—
popup_buttonAXPopUpButtoncombo boxComboBox
combo_boxAXComboBoxcombo boxComboBox
linkAXLinklinkHyperlink
checkboxAXCheckBoxcheck box, check menu itemCheckBox
radioAXRadioButtonradio button, radio menu itemRadioButton
switchAXSwitch †switch, toggle button—
disclosureAXDisclosureTriangle——
text_fieldAXTextFieldentry, password textEdit
text_areaAXTextAreaentryEdit
search_fieldAXSearchField †entryEdit
sliderAXSlidersliderSlider
stepperAXIncrementorspin buttonSpinner
tabAXTabButton †page tabTabItem
menu_itemAXMenuItemmenu itemMenuItem
menubar_itemAXMenuBarItem——
dock_itemAXDockItem——
cellAXCelltable cellDataItem
rowAXRowtable rowTreeItem
list_itemAXRowlist itemListItem
imageAXImageimage, iconImage
static_textAXStaticTextstatic, label, textText
headingAXHeadingheading—
color_wellAXColorWellcolor chooser—
toolbar_buttonAXToolbarButton †——

A — means the platform has no equivalent and the entry is ignored there. neru config validate warns about such an entry once your clickable_roles differs from the shipped list, and always for additional_clickable_roles. neru roles --explain and neru doctor always report it.

† A subrole. AppKit reports it in the element’s subrole while the role stays generic, e.g. a search field is an AXTextField with subrole AXSearchField. Neru matches names against both role and subrole, so these work as written.

Address any native role directly with its vocabulary prefix:

clickable_roles = [
"button",
"ax:AXDisclosureTriangle", # macOS only
"atspi:page tab list", # Linux only
"uia:Custom", # Windows only
]

Prefixed entries for another platform are ignored, not rejected. Many legacy Win32 and WinForms controls appear only as uia:Pane, uia:Custom or uia:Document and need naming directly. An unprefixed entry must be a semantic role, and an unknown one is a config error:

hints.clickable_roles: unknown role "AXButton": use "button"
OptionTypeDefaultDescription
font_sizeint10Font size in points
font_familystring""Font family. Accepts generic aliases, and empty means the platform’s sans family
border_radiusint-1Corner radius, -1 for automatic
padding_xint-1Horizontal padding, -1 for automatic
padding_yint-1Vertical padding, -1 for automatic
border_widthint1Border width in pixels
placementstring"bottom"Label placement relative to the element: top, center, bottom. top and bottom draw a connector arrow
background_colorcolorderivedBackground color
text_colorcolorderivedText color
matched_text_colorcolorderivedText color for matched characters
border_colorcolorderivedBorder color

Element outlines for dense layouts, in [hints.boundary_highlight].

OptionTypeDefaultDescription
enabledboolfalseDraw element boundaries
border_widthint1Stroke width in pixels
border_radiusint-1Corner radius (-1 = auto pill)
background_colorcolorderivedElement fill color
border_colorcolorderivedElement stroke color

[hints.search_input_ui] also takes the hints UI options except placement and matched_text_color.

OptionTypeDefaultDescription
positionstring"bottom_center"Anchor: top_left, top_center, top_right, center, bottom_left, bottom_center, bottom_right
x_offsetint0Horizontal offset from anchor
y_offsetint24Vertical offset from anchor
widthint320Width in pixels

[hints.vision] is read only when the global or per-app strategy is "vision". The rectangle and confidence options do nothing on some platforms, see Platform support per word.

OptionTypeDefaultDescription
detect_textbooltrueEnable text detection. With this off, Linux detects nothing
detect_rectanglesbooltrueEnable rectangle detection
request_timeout_msint5000Timeout for one analysis request (one OCR pass on Linux), in ms
minimum_confidencefloat0.0Minimum confidence (0.0 to 1.0) for keeping an observation
merge_iou_thresholdfloat0.5Intersection-over-Union overlap at which boxes merge
rectangle_max_candidatesint100Maximum rectangle candidates to evaluate
rectangle_min_sizefloat0.01Minimum rectangle size as a fraction of the captured region (0.01 is 1%)
rectangle_min_aspectfloat0.3Minimum rectangle aspect ratio (width/height)
rectangle_max_aspectfloat10.0Maximum rectangle aspect ratio (width/height)
button_min_confidencefloat0.3Minimum confidence for classifying a rectangle as a button
button_min_aspectfloat0.8Minimum aspect ratio for buttons
button_max_aspectfloat8.0Maximum aspect ratio for buttons
button_icon_max_sizeint48Maximum width/height in pixels for square buttons or icons
link_min_aspectfloat5.0Minimum aspect ratio for text links
link_max_heightint40Maximum height in pixels for text links
link_min_widthint50Minimum width in pixels for text links
image_min_sizeint48Minimum width/height in pixels for images
checkbox_max_sizeint32Maximum width/height in pixels for checkboxes
generic_clickable_min_confidencefloat0.5Minimum confidence for generic clickable elements

[hints.contour] works on all three platforms. Defaults are wl-kbptr’s. Sizes are logical pixels (points on Retina). Edge thresholds are Sobel gradient magnitudes on a 0 to 255 grayscale frame, at most 1530. Lower them for faint outlines on low-contrast themes, and widen the target bounds to hint notification cards and toasts.

OptionTypeDefaultDescription
request_timeout_msint2000Time budget for one pass. A pass over budget returns no targets
edge_low_thresholdint70Canny low threshold, which extends an edge. Must be at most the high value
edge_high_thresholdint220Canny high threshold, which starts an edge. Lower finds fainter outlines
min_target_widthfloat7.0Blobs this wide or narrower are noise
min_target_heightfloat3.0Blobs this tall or shorter are noise
max_target_widthfloat650.0Blobs this wide or wider are layout containers
max_target_heightfloat160.0Blobs this tall or taller are layout containers
flat_line_heightfloat6.0Nested strokes no taller than this are dropped (hamburger lines, underlines)
container_heightfloat50.0A blob this tall holding button-sized children is a card and is dropped for them
same_center_slackfloat8.0A nested blob whose center is this close to its parent’s duplicates the parent
square_icon_sizefloat40.0A roughly square parent smaller than this keeps its box and drops its inner detail
square_icon_slackfloat5.0How far from square (width minus height) that parent may be

label_direction sets how multi-character labels are enumerated once single-character labels run out. With asdf and 5 elements:

DirectionSequenceNotes
normal (default)A S D FA FSKeeps 3 single-char labels, then expands the 4th alphabet slot into 2-char labels.
reverseAA SA DA FA ASFills the 2-char tier uniformly from the first alphabet character.

Use normal for most workflows and for short alphabets, since it needs fewer keystrokes. Use reverse when many hints cluster in one region or you regularly need more than len(hint_characters) hints, since it spreads first characters evenly. Set it per app in the per-app config or per activation with neru hints --label-direction.

[hints.hotkeys] # plus the shared defaults under Per-mode hotkeys
"/" = "action search_hints"
"Backspace" = "action backspace"
"Tab" = "action cycle_hint"
"Shift+Tab" = "action cycle_hint --backward"

An empty string falls back to the global value.

FieldTypeDescription
bundle_idstringApp bundle ID
strategystring"axtree", "vision" or "contour"
capture_scopestring"window" or "screen"
label_directionstring"normal" or "reverse"
additional_clickable_rolesarrayExtra roles to treat as clickable, same vocabulary as clickable_roles
ignore_clickable_checkboolSkip clickability heuristic for this app
visible_check_enabledboolEnable visibility hit-test for this app
hotkeysmapper-app hotkey overrides

Settings for grid mode. How the mode behaves is in neru grid.

[grid]
characters = "asdfghjkl"
capture_scope = "window"
OptionTypeDefaultDescription
enabledbooltrueTurn grid mode on or off
capture_scopestring"screen"Region the grid covers: screen or window (the screen if nothing is focused). --capture-scope overrides it
charactersstring"abcdefghijklmnpqrstuvwxyz"Primary grid labels. Cannot be empty or contain non-ASCII
sublayer_keysstring"abcdefghijklmnpqrstuvwxyz"Subgrid labels, first 9 used (3×3). Empty uses the grid’s label characters. Cannot contain non-ASCII
max_label_lengthint4Maximum coarse-grid label length (2 to 4). A lower limit enlarges the coarse grid to still cover the screen
row_labelsstring""Custom row labels. Empty infers them from characters
col_labelsstring""Custom column labels. Empty infers them from characters
live_match_updatebooltrueHighlight cells as you type
hide_unmatchedbooltrueHide non-matching cells
prewarm_enabledbooltruePre-compute grid on startup
enable_gcboolfalsePeriodic memory cleanup

neru config validate warns, without refusing the file, when characters, row_labels or col_labels has a single character, a repeat (case-folded, so aA repeats), whitespace or a control character, or when row_labels or col_labels has non-ASCII.

  • A repeat is dropped, in every set including sublayer_keys.
  • A characters with fewer than two distinct characters falls back to a-z.
  • Short row or column labels cap the grid to the cells they can name.
  • An empty label set is checked and reported as characters.
  • sublayer_keys is not checked for these faults. A 9-character set with a repeat leaves one subgrid cell unlabeled.
OptionTypeDefaultDescription
font_sizeint10Largest label size in points. Labels shrink, at one size for the whole grid, to fit small cells, and never hide
font_familystring""Font family. Accepts generic aliases, and empty means the platform’s sans family
border_widthint1Border width in pixels
background_colorcolorderivedCell background
text_colorcolorderivedLabel text
matched_text_colorcolorderivedMatched cell text
matched_background_colorcolorderivedMatched cell background
matched_border_colorcolorderivedMatched cell border
border_colorcolorderivedDefault cell border
[grid.hotkeys] # plus the shared defaults under Per-mode hotkeys
"`" = "toggle-cursor-follow-selection"
"Space" = "action reset"
"Backspace" = "action backspace"
FieldTypeDescription
bundle_idstringApp bundle ID
capture_scopestringscreen or window region for this app
hotkeysmapper-app hotkey overrides

Settings for recursive grid mode. How the mode behaves is in neru recursive_grid.

[recursive_grid]
grid_cols = 2
grid_rows = 2
keys = "uijk" # one key per cell
OptionTypeDefaultDescription
enabledbooltrueTurn the mode on or off
capture_scopestring"screen"Region the first level covers: screen or window (the screen if nothing is focused). --capture-scope overrides it
grid_colsint3Columns (≥ 1, total cells ≥ 2)
grid_rowsint3Rows (≥ 1, total cells ≥ 2)
keysstring"rtyfghvbn"Cell selection keys (must be grid_cols × grid_rows characters)
min_size_widthint1Minimum cell width, in apparent pixels (scaled with the display on Windows and X11)
min_size_heightint1Minimum cell height, in apparent pixels (scaled with the display on Windows and X11)
max_depthint10Maximum recursion levels (1 to 20)
layersarray[]Per-depth layout overrides (see below)

Each entry overrides the grid dimensions and keys at one depth:

FieldTypeDefaultDescription
depthintrequiredDepth to override (0-based)
grid_colsintsame as parentColumns at this depth
grid_rowsintsame as parentRows at this depth
keysstringsame as parentSelection keys at this depth
[recursive_grid]
layers = [
{ depth = 0, grid_cols = 2, grid_rows = 2, keys = "crtn" },
{ depth = 1, grid_cols = 3, grid_rows = 3, keys = "gcrhtnmwv" },
]
OptionTypeDefaultDescription
enabledbooltrueNative depth transitions on supported platforms
duration_msint50Transition duration, in ms

Labels shrink to fit narrowing cells, keeping each cell at least label_autohide_multiplier times the font size, and hide below min_font_size. All labels in one draw share a size. Set min_font_size to font_size to never shrink. The sub-key preview follows the same rule with its own size and multiplier.

OptionTypeDefaultDescription
font_sizeint10Largest label size
font_familystring""Font family. Accepts generic aliases, and empty means the platform’s sans family
line_widthint1Grid line width
line_colorcolorderivedGrid line color
highlight_colorcolorderivedSelected cell highlight
text_colorcolorderivedLabel text
label_backgroundboolfalseBackground behind labels
label_background_colorcolorderivedLabel background
label_background_padding_xint-1Horizontal label padding, -1 for automatic
label_background_padding_yint-1Vertical label padding, -1 for automatic
label_background_border_radiusint-1Label corner radius, -1 for automatic
label_background_border_widthint1Label border width
label_charstring""Override all cell labels with a single character (e.g. ·), empty = use key
min_font_sizeint6Smallest size a label or the sub-key preview shrinks to before it hides
label_autohide_multiplierfloat1.5Keep cell >= fontSize × multiplier by shrinking the label, 0 turns it off
sub_key_previewboolfalseShow a mini-grid of the next level’s keys inside each cell
sub_key_preview_font_sizeint8Sub-key preview font size
sub_key_preview_autohide_multiplierfloat1.5The same requirement for the preview, measured against one sub-cell
sub_key_preview_text_colorcolorderivedSub-key preview text color
sub_key_preview_label_charstring""Override sub-key labels with a single character (e.g. ·), empty = use key

The same as grid: ` toggles cursor follow, Space resets, and Backspace steps back.

[[recursive_grid.app_configs]] takes bundle_id, capture_scope and hotkeys, as in grid.

Settings for bisect mode. How the mode behaves is in neru bisect.

[bisect]
capture_scope = "window"
OptionTypeDefaultDescription
enabledbooltrueTurn the mode on or off
capture_scopestring"screen"Region the session starts from: screen or window (the screen if nothing is focused). --capture-scope overrides it

The quadrant cells show whichever single-character keys are bound to the quadrant cuts.

[bisect.hotkeys] # plus the shared defaults under Per-mode hotkeys
"`" = "toggle-cursor-follow-selection"
"h" = "action bisect --direction=left"
"j" = "action bisect --direction=down"
"k" = "action bisect --direction=up"
"l" = "action bisect --direction=right"
"y" = "action bisect --direction=up_left"
"u" = "action bisect --direction=up_right"
"b" = "action bisect --direction=down_left"
"n" = "action bisect --direction=down_right"
"Space" = "action reset"
"Backspace" = "action backspace"
OptionTypeDefaultDescription
enabledbooltrueNative transition between cuts on supported platforms
duration_msint50Transition duration, in ms

The same options and defaults as [recursive_grid.ui]. label_char overrides the four quadrant labels, and label_autohide_multiplier and min_font_size shrink and then hide them. The sub_key_preview* options are accepted and do nothing.

[[bisect.app_configs]] takes bundle_id, capture_scope and hotkeys, as in grid.

Keyboard-driven scrolling.

[scroll]
scroll_step = 80
invert_scroll = true
OptionTypeDefaultDescription
scroll_stepint50Pixels per line scroll action
scroll_step_halfint500Pixels per half-page action
scroll_step_fullint1000000Pixels for top/bottom jump actions
invert_scrollboolfalseInvert scroll direction (for tools like Mos that reverse synthetic scroll events)
[scroll.hotkeys] # plus the shared defaults under Per-mode hotkeys
"k" = "action scroll_up"
"j" = "action scroll_down"
"h" = "action scroll_left"
"l" = "action scroll_right"
"gg" = "action go_top"
"Shift+G" = "action go_bottom"
"u" = "action page_up"
"PageUp" = "action page_up"
"d" = "action page_down"
"PageDown"= "action page_down"
FieldTypeDescription
bundle_idstringApp bundle ID
scroll_stepintscroll_step for this app
scroll_step_halfintscroll_step_half for this app
scroll_step_fullintscroll_step_full for this app
hotkeysmapper-app hotkey overrides

Picks a display by typing the label on its badge. Monitors are ordered top to bottom, then left to right.

[monitor_select]
enabled = true
characters = "asdf"
OptionTypeDefaultDescription
enabledboolfalseEnable interactive monitor picking
charactersstring"123456789"Characters used for monitor labels
KeyDefaultDescription
font_size96Largest badge label size. It shrinks to fit the badge, which is capped at 80% of the monitor
font_family"" (sans)Badge label font family. Accepts generic aliases, and empty means sans
subtitle_font_size18Largest monitor name size. It shrinks so a long name fits the badge
subtitle_font_family"" (label’s)Subtitle font family, defaulting to the label’s. Accepts generic aliases
border_radius-1 (auto)Badge corner radius
padding_x-1 (auto)Horizontal padding
padding_y-1 (auto)Vertical padding
border_width1Badge border width
background_colorderivedBadge fill color
text_colorderivedLabel text color
matched_text_colorderivedPartially-typed label text color
border_colorderivedBadge border color
backdrop_color"" (none)Per-monitor overlay backdrop tint
subtitle_text_colorderivedSubtitle text color

[monitor_select.hotkeys] defaults to "Escape" = "idle".

Styles the pointer character Neru draws in place of the cursor. The standalone overlay, drawn when hide_cursor hides the system cursor, is macOS-only. The in-frame indicator in grid and recursive-grid overlays uses the same options on every platform.

[virtual_pointer.ui]
char = "+"
font_size = 12
OptionTypeDefaultDescription
charstring"●"Character to display
font_sizeint8Font size in points
font_familystring""Font family. Accepts generic aliases, and empty means the platform’s sans family
text_colorcolorderivedCharacter color

A transient marker drawn where a mouse action happens. Works on all platforms, and animation timing may differ slightly between them.

[mouse_action_indicator]
enabled = true
actions = ["left_click", "right_click"]
[mouse_action_indicator.ui]
shape = "square"
OptionTypeDefaultDescription
enabledboolfalseEnable indicators
actionsstring[]every click, press, release, and toggle actionTriggering actions

actions accepts any mouse button action from the action names that a mode --action accepts.

OptionTypeDefaultDescription
sizeint36Diameter in points
border_widthint2Border width
background_colorcolorderivedFill color
border_colorcolorderivedStroke color
shapestring"circle"circle or square
OptionTypeDefaultDescription
duration_msint260Animation duration, in ms
start_scalefloat0.55Starting scale
end_scalefloat1.35Ending scale
start_opacityfloat0.85Starting opacity
end_opacityfloat0.0Ending opacity
easingstring"ease_out"linear, ease_in, ease_out, ease_in_out

A floating label that follows the cursor and shows the current mode.

[mode_indicator.hints]
enabled = true
text = "H"

Each mode has a [mode_indicator.<mode>] table for scroll, hints, grid, recursive_grid, bisect and monitor_select. Only scroll is shown by default, and the default text is the mode’s name in title case, e.g. Recursive Grid.

OptionTypeDefaultDescription
enabledboolvaries by modeShow the indicator for this mode
textstringvaries by modeLabel text
background_colorcolorderivedOverride background color
text_colorcolorderivedOverride text color
border_colorcolorderivedOverride border color
OptionTypeDefaultDescription
font_sizeint10Font size
font_familystring""Font family. Accepts generic aliases, and empty means the platform’s sans family
background_colorcolorderivedBackground with alpha
text_colorcolorderivedText color
border_colorcolorderivedBorder color
border_widthint1Border width
padding_xint-1Horizontal padding, -1 for automatic
padding_yint-1Vertical padding, -1 for automatic
border_radiusint-1Corner radius, -1 for automatic
indicator_x_offsetint20X offset from cursor (positive = right)
indicator_y_offsetint20Y offset from cursor (positive = down)

Tap a modifier inside a mode to hold it for the following actions.

[sticky_modifiers]
tap_max_duration = 200
OptionTypeDefaultDescription
enabledbooltrueEnable sticky modifiers
tap_max_durationint300Longest press that counts as a tap, in ms. 0 always toggles

[sticky_modifiers.ui] takes the same options and defaults as [mode_indicator.ui], except indicator_x_offset defaults to -40 (left of the cursor).

On Linux the indicator draws ❖⇧⌥⌃. If they show as boxes, set font_family to a font with those glyphs.

Animates cursor movement. Off by default.

[smooth_cursor]
move_mouse_enabled = true
max_duration = 150
OptionTypeDefaultDescription
move_mouse_enabledboolfalseEnable animated mouse movement
stepsint10Number of animation steps
max_durationint200Longest animation, in ms
duration_per_pixelfloat0.1Ms per pixel for jumps, giving constant speed
relative_movement_durationint50Fixed duration per relative move, in ms (>= 10)

relative_movement_duration applies to move_mouse_relative, which the default arrow bindings use. Each relative move takes this fixed time, so cursor speed scales with the step size. A move arriving mid-animation extends the current endpoint, so no distance is lost under key repeat or held_repeat acceleration.

Splits each scroll into chunked ease-out events. How fine a step can be depends on the platform, see Platform support.

[smooth_scroll]
enabled = true
OptionTypeDefaultDescription
enabledboolfalseEnable smooth scrolling
stepsint20Number of animation steps
max_durationint180Longest animation, in ms
duration_per_pixelfloat1.0Ms per pixel for adaptive duration
  • On Wayland, enabling it sends a continuous delta instead of wheel notches, which some apps scale differently. Trim scroll.scroll_step if the distance changes.
  • Under [held_repeat], each repeat folds in what the previous animation had not yet sent, so N repeats travel as far as N presses (within a wheel notch per repeat on X11).
  • A scroll with a different modifier set cancels the animation in flight and drops its remaining distance.

Repeats scroll, page and move_cell actions while the key is held, and glides the cursor for a held move_mouse_relative (see Glide).

[held_repeat]
enabled = true
accel_enabled = true
OptionTypeDefaultDescription
enabledboolfalseMaster toggle for held-key repeat and the glide
initial_delay_msint50Delay before the first repeat, in ms
interval_msint50Interval between repeats, in ms
accel_enabledboolfalseRamp the glide’s speed up the longer the key stays held
accel_ramp_msint500Hold time to reach accel_max_multiplier, in ms
accel_max_multiplierfloat4.0Speed multiplier at full ramp
accel_targetsstring[]["move_mouse_relative"]Action names eligible for acceleration

A held key bound to a lone move_mouse_relative glides the cursor in the direction of its --dx/--dy instead of repeating. Speed is the binding’s step per interval_ms (10px every 50ms is 200px/s) from key down to release, so initial_delay_ms does not apply. Two held keys move diagonally at the same speed, opposite keys cancel, and the larger step sets the speed. A short tap travels about one step. The glide works across monitors, and a click during it acts at the cursor’s live position.

With accel_enabled = true, the glide speed ramps linearly to accel_max_multiplier times the binding’s speed over accel_ramp_ms. With the defaults a 10px binding reaches 500px/s at 250ms and 800px/s from 500ms.

  • accel_targets accepts only move_mouse_relative. Any other entry, or an empty list while accel_enabled = true, is a config error.
  • accel_enabled = true with enabled = false does nothing, and neru config validate warns.

The system tray icon and its menu.

[systray]
enabled = false
OptionTypeDefaultDescription
enabledbooltrueShow the tray icon

A change to enabled needs a daemon restart, since neru config reload keeps the icon as is. On Windows, notifications need the tray, see Platform support.

Neru always logs to the console. Set disable_file_logging = false to also write a JSON log file. Its path is under Log File Locations.

[logging]
log_level = "debug"
disable_file_logging = false
OptionTypeDefaultDescription
log_levelstring"info"Level: debug, info, warn, error
disable_file_loggingbooltrueLog to the console only. Set false to also write a JSON log file
log_filestring""Log file path when file logging is on. Empty uses the platform default path
max_file_sizeint10MB before the log file is rotated
max_backupsint5Rotated log files to keep
max_ageint30Days to keep rotated log files

info covers daemon start (with the config path) and stop, config reloads, and mode activation and exit. warn marks something Neru worked around, and error a failure it could not recover from. Use debug temporarily for key routing, hint generation, overlays or IPC. No level logs typed text, fed keys, exec output or config values.