TUIOSTUIOS

Configuration

Customize TUIOS keybindings and appearance

TUIOS reads a single TOML file, ~/.config/tuios/config.toml, following the XDG Base Directory specification. It has exactly four top-level tables: [appearance], [keybindings], [daemon], and [hooks].

Custom colour themes are a separate mechanism. They are JSON files in ~/.config/tuios/themes/, not entries in the TOML file. See Custom themes.

Quick Start

Find your configuration

tuios config path

Edit configuration

tuios config edit

View current keybindings

tuios keybinds list

Reset to defaults

tuios config reset

Configuration file location

Default path: ~/.config/tuios/config.toml

On first launch, TUIOS writes a default configuration file. The exact location follows the XDG Base Directory specification:

  • Linux/macOS: ~/.config/tuios/config.toml
  • Custom: $XDG_CONFIG_HOME/tuios/config.toml (if XDG_CONFIG_HOME is set)

You only need to specify what you want to change. Missing keys and whole missing sections are filled in with defaults at load time, so a three-line file is a perfectly valid config.

# ~/.config/tuios/config.toml

[keybindings.window_management]
new_window = ["ctrl+t"]
close_window = ["ctrl+w"]

Applying changes

TUIOS watches the config file and reloads it automatically. A write is debounced for 200ms, the file is re-parsed, and the result is applied on the render loop. There is also a Reload Config entry in the command palette (Ctrl+P) if you want to force it.

Only part of the config reloads

Hot reload re-applies a specific subset of [appearance]. Everything else needs a restart, and nothing tells you which is which at the time you edit.

Reloads live: animations_enabled, confirm_quit, shared_borders, whichkey_enabled, whichkey_position, window_title_format, scroll_lines, zoom_max_width, border_focused_color, border_unfocused_color. Also window_title_position, but only while the current value is still the default bottom.

Needs a restart: every keybinding, leader_key, theme, border_style, dockbar_position, hide_window_buttons, hide_scrollbar, show_clock, show_cpu, show_ram, scrollback_lines, max_fps, niri_reverse_scroll, preferred_shell, the whole [daemon] table, and the whole [hooks] table.

Hooks in particular are read once at startup. Editing [hooks] and saving does nothing until you restart the client.

If the reloaded file fails to parse or has errors, the reload is abandoned and the running config stays in place. The failure is written to the log, not shown on screen, so a silently unchanged setting usually means the file did not parse.

Appearance

The [appearance] table controls visual presentation. All 24 keys:

KeyTypeDefaultCLI flag
themestring""--theme
border_styleenum"rounded"--border-style
border_focused_colorhex string""none
border_unfocused_colorhex string""none
dockbar_positionenum"bottom"--dockbar-position
hide_window_buttonsboolfalse--hide-window-buttons
hide_scrollbarboolfalse--hide-scrollbar
shared_bordersboolfalse--shared-borders
window_title_positionenum"bottom"--window-title-position
window_title_formatstring""none
scrollback_linesint, 100 to 100000010000--scrollback-lines
scroll_linesint, 1 to 503none
zoom_max_widthint, 0 means fullscreen0--zoom-max-width
max_fpsint, 10 to 2400, meaning 60none
animations_enabledbooltrue--no-animations
confirm_quitboolfalse--confirm-quit
whichkey_enabledbooltruenone
whichkey_positionenum"bottom-right"none
show_clockboolfalse--show-clock
show_cpuboolfalse--show-cpu
show_ramboolfalse--show-ram
hide_clockbool, deprecatedfalse--hide-clock
preferred_shellstring""none
niri_reverse_scrollboolfalsenone

border_style

Valid values: "rounded" (default), "normal", "thick", "double", "hidden", "block", "ascii", "outer-half-block", "inner-half-block".

"hidden" does more than remove the border characters. It also suppresses the window control buttons and the scrollbar thumb, because both are drawn on the border and there is no border left to draw them on. You do not need to set hide_window_buttons or hide_scrollbar alongside it.

border_focused_color and border_unfocused_color

Hex colours that override the theme's border colours, for example "#89b4fa" and "#585b70". Empty clears the override and restores the theme's own colours.

dockbar_position

Valid values: "bottom" (default), "top", "hidden".

window_title_position

Valid values: "bottom" (default), "top", "hidden".

When hidden, the rename window keybinding is disabled.

window_title_format

A template applied to every window title. Three placeholders are expanded:

  • {title} - the window's custom name, or the title the terminal set
  • {index} - the window's 1-based position in its workspace
  • {cwd} - the window's working directory
[appearance]
window_title_format = "{index}: {title} ({cwd})"

Anything written in braces that is not one of those three produces a validation warning and is rendered literally in the title bar. A format that mentions only {index} or {cwd} still renders for a window that has no title of its own.

scrollback_lines

Lines kept per terminal window. Clamped to 100 minimum, 1,000,000 maximum.

This is read when a window is constructed. Changing it applies to windows you open afterwards, never to windows already on screen.

scroll_lines

Lines moved per mouse wheel notch, in scrollback, copy mode, and the scrollback browser. Clamped to 1 minimum, 50 maximum.

zoom_max_width

Maximum width in cells for zoom mode. 0 means fill the screen. Setting it to, say, 120 centres the zoomed window at 120 columns instead of stretching it across an ultrawide display.

max_fps

Render frame-rate cap. 0 leaves it at the default of 60. Any other value is clamped to the range 10 to 240.

The comment on this field in the source says the maximum is 120. The code clamps to 240, and 240 is what you get.

hide_clock

Deprecated. The clock is hidden by default now, so use show_clock to turn it on. hide_clock still takes precedence when true.

The clock is drawn during tape recording and while the leader key is pending regardless of this setting.

preferred_shell

The shell to launch in new windows. Empty means auto-detect for the platform.

niri_reverse_scroll

Reverses the mouse scroll direction in the scrolling (niri-style) layout.

Themes

TUIOS ships 342 built-in themes, supplied by the bubbletint dependency.

# List every theme
tuios --list-themes

# Preview one theme's 16 ANSI colors
tuios --preview-theme dracula

# Launch with a theme
tuios --theme dracula
[appearance]
theme = "dracula"

The theme picker

There is a searchable theme picker with fuzzy matching, an 8-colour swatch per theme, and live preview as you move through the list. Esc restores whatever theme you had before you opened it.

The picker has no keybinding

There is no prefix command and no configurable action for the theme picker. The only two ways to open it are the Theme row on the settings page (press Enter on it) and the Theme Picker entry in the Ctrl+P command palette.

Custom themes

Custom themes are JSON, one theme per file, in ~/.config/tuios/themes/. The directory is flat: subdirectories are skipped, not searched.

{
  "id": "my-theme",
  "display_name": "My Theme",
  "dark": true,
  "fg": "#e0def4",
  "bg": "#191724",
  "black": "#26233a",
  "red": "#eb6f92",
  "green": "#31748f",
  "yellow": "#f6c177",
  "blue": "#9ccfd8",
  "purple": "#c4a7e7",
  "cyan": "#ebbcba",
  "white": "#e0def4"
}

Save that as ~/.config/tuios/themes/my-theme.json and select it with theme = "my-theme" or --theme my-theme.

  • id defaults to the filename with the extension stripped and lowercased, so it can be omitted.
  • display_name defaults to id.
  • Missing colours are filled with xterm defaults rather than rejected.
  • Each bright_* colour falls back to its non-bright counterpart when absent, which is why the example above omits all eight.
  • A file that does not parse is logged and skipped. It does not stop the other themes loading, and it does not stop TUIOS starting. Nothing appears on screen, so check the log viewer if a theme does not show up.

The themes directory is read once per process, behind a one-shot guard. Adding a new .json file while TUIOS is running will not make it appear, and neither will the config reload. Restart.

Keybindings

leader_key

The tmux-style prefix key for prefix commands.

[keybindings]
# Ctrl+A, like GNU Screen
leader_key = "ctrl+a"

Other useful values: "alt+space", "ctrl+space".

The 14 sections

Every one of these is a table under [keybindings], and every one resolves through the keybind registry, so every action in every one of them is rebindable.

SectionContext
window_managementWindow mode: create, close, rename, minimize, cycle, select by number
workspacesSwitch workspace, move window to workspace and follow
layoutSnapping, tiling toggle, swaps, resizes, BSP splits, preselect
mode_controlEnter terminal mode, enter window mode, toggle help, quit
navigationArrow navigation and selection extension
restore_minimizedRestore minimized window 1-9
systemShips empty by default
prefix_modeAfter the leader key
window_prefixAfter leader then t
minimize_prefixAfter leader then m
workspace_prefixAfter leader then w
debug_prefixAfter leader then D
tape_prefixAfter leader then T
terminal_modeDirect binds in terminal mode, no prefix needed

The prefix sections used to be inert

Six of these sections were parsed and then ignored. Rebinding anything under the leader key was a silent no-op: your config was accepted, and the hard-coded key kept working instead.

prefix_mode, window_prefix, minimize_prefix, workspace_prefix, debug_prefix, and tape_prefix now all resolve through the registry, so they do what they say. terminal_mode was fixed at the same time, though it is not a prefix section.

If you wrote bindings for these sections against an older version and gave up on them, they work now.

Section contents

window_management actions: new_window, close_window, rename_window, minimize_window, restore_all, toggle_zoom, next_window, prev_window, select_window_1 through select_window_9.

workspaces actions: switch_workspace_1 through switch_workspace_9, move_and_follow_1 through move_and_follow_9.

layout actions: snap_left, snap_right, snap_fullscreen, unsnap, snap_corner_1 through snap_corner_4, toggle_tiling, swap_left, swap_right, swap_up, swap_down, resize_master_shrink, resize_master_grow, resize_height_shrink, resize_height_grow, resize_master_shrink_left, resize_master_grow_left, resize_height_shrink_top, resize_height_grow_top, split_horizontal, split_vertical, rotate_split, equalize_splits, preselect_left, preselect_right, preselect_up, preselect_down.

mode_control actions: enter_terminal_mode, enter_window_mode, toggle_help, quit.

navigation actions: nav_up, nav_down, nav_left, nav_right, extend_up, extend_down, extend_left, extend_right.

restore_minimized actions: restore_minimized_1 through restore_minimized_9.

terminal_mode actions: terminal_next_window, terminal_prev_window, terminal_exit_mode.

The prefix sections are listed in full on the Keybindings reference, alongside their default keys.

terminal_mode defaults are platform-specific

# macOS
[keybindings.terminal_mode]
terminal_next_window = ["opt+tab", "alt+n"]
terminal_prev_window = ["opt+shift+tab", "alt+p"]
terminal_exit_mode = ["opt+esc"]

# Linux and elsewhere
[keybindings.terminal_mode]
terminal_next_window = ["alt+n"]
terminal_prev_window = ["alt+p"]
terminal_exit_mode = ["alt+esc"]

One key that config cannot claim

A bare , in window mode is intercepted for the settings page before any keybinding lookup happens.

Two consequences. The default resize_master_shrink_left binding on , is unreachable in window mode. And rebinding , under [keybindings.layout] to something else will still open settings, because the intercept is a hardcoded comparison rather than a registry lookup. This is the one place where the config does not win.

Key syntax

Modifiers

  • ctrl+
  • alt+
  • shift+
  • opt+, option+ (macOS only)

cmd+ and super+ are not supported; they are usually captured by the OS before TUIOS sees them.

Special keys

enter, return, esc, escape, tab, space, backspace, delete, up, down, left, right, home, end, pgup, pgdown, f1 through f12.

Multiple keys per action

new_window = ["n", "ctrl+n", "ctrl+t"]

Removing a binding

close_window = []

An empty array unbinds the action. It produces a warning, not an error, since some actions are reasonably left unbound.

Shifted keys have two spellings, and both match

shift+1 and ! are treated as the same key. So are shift+a and A. Binding either spelling binds both, on every platform.

This is convenient until it is not. If you bind shift+1 to one action and ! to another, they collide, and one of them wins non-deterministically. Pick one spelling per key. The default config leans on this deliberately, which is why restore_minimized_1 lists both "shift+1" and "!".

Platform notes

macOS

Workspace switching defaults to the Option key:

[keybindings.workspaces]
switch_workspace_1 = ["opt+1"]
switch_workspace_2 = ["opt+2"]

opt+1, option+1, and alt+1 all work. On macOS, writing alt+ produces an advisory warning suggesting opt+ instead, for consistency with what is printed on the key. It is advice, not an error, and the binding works either way.

Linux and elsewhere

Use alt+ and ctrl+. opt+ and option+ are not valid.

The in-app settings page

TUIOS has a settings page that edits the config for you. It exposes 19 options in four categories:

CategoryOptions
AppearanceTheme, Border style, Window title, Shared borders, Window buttons, Scrollbar
DockDock position, Clock, CPU meter, RAM meter
BehaviorAnimations, Confirm quit, Which-key, Which-key position, Reverse scroll, Max FPS
AdvancedScrollback lines, Scroll lines, Zoom width

Three ways in:

  • Ctrl+B then , (the prefix_settings action)
  • a bare , in window mode
  • the Settings entry in the Ctrl+P command palette

The settings page rewrites your config file

Every adjustment you make persists immediately, by serialising the in-memory config and overwriting ~/.config/tuios/config.toml wholesale. There is no merge and no round-trip.

That means your comments are gone, your key ordering is gone, your formatting is gone, and every field that was previously absent gets written out with its default value. A hand-tuned 10-line config comes back as a full dump.

If you maintain the file by hand, keep a copy in version control before you touch the settings page.

Most of the page applies as you adjust it. Two exceptions worth knowing:

  • Scrollback lines applies to windows opened afterwards, not to windows already on screen.
  • Theme, if you point it at a custom .json you just added, needs a restart. The themes directory is only read once per process.

The daemon table

[daemon]
log_level = "off"

log_level sets the daemon's debug verbosity. Valid values, in increasing order: off (default), errors, basic, messages, verbose, trace. It is read when a foreground daemon starts, and only if the level was not already set another way.

default_codec and socket_path are declared and inert

The default config file that TUIOS writes for you contains two more daemon keys:

[daemon]
default_codec = "gob"
socket_path = ""

Both are parsed, both are given defaults, both are written into every user's config file, and neither is read by anything. Setting default_codec = "json" does not change the wire codec. Setting socket_path does not move the socket.

They are documented here only so you do not spend an evening working out why they have no effect. The socket location is fixed; see Sessions.

Hooks

The [hooks] table runs shell commands on window, workspace, and session events. It has its own page: Hooks.

Two things to carry over from there: hook event names are hyphenated rather than snake_case, and a misspelled event name is silently ignored.

Validation

The config is validated at load. Errors and warnings are treated differently.

Errors are things like an unparseable key ("cmd+t", "ctrl+"). They are printed to stderr:

Config error in [window_management]: cmd+t - invalid modifier: cmd

An error does not stop TUIOS from starting, and it does not fall back for just the broken key. The whole config file is discarded and the full built-in defaults are used instead. One bad key costs you every customisation in the file.

Since this is printed before the alternate screen is entered, the first rendered frame usually wipes it. Run tuios keybinds list to see it without the TUI on top.

Warnings do not discard anything. They cover unknown enum values (which fall back to their defaults), unknown title-format placeholders, actions bound to no keys, keys bound to more than one action in the same context, essential actions left unbound, and the macOS alt+ advisory. They are surfaced inside the running TUI as a notification pointing you at the log viewer.

Auditing your customisations

tuios keybinds list-custom

This is not a complete audit

list-custom diffs 9 of the 14 keybinding sections: window_management, workspaces, layout, mode_control, system, prefix_mode, window_prefix, minimize_prefix, workspace_prefix.

It does not look at navigation, restore_minimized, debug_prefix, tape_prefix, or terminal_mode, and it does not report a customised leader_key at all. A config that changes only those will print "No custom keybindings configured", which is wrong.

Treat the output as a partial view. For the whole picture, read the file.

Example configurations

Browser-style

[keybindings.window_management]
new_window = ["ctrl+t"]
close_window = ["ctrl+w"]
next_window = ["ctrl+tab"]
prev_window = ["ctrl+shift+tab"]

Vim-style

[keybindings.mode_control]
enter_terminal_mode = ["i", "a"]
enter_window_mode = ["esc"]

[keybindings.window_management]
new_window = ["ctrl+t"]
close_window = ["ctrl+w"]

Screen-style leader with a rebound prefix menu

[keybindings]
leader_key = "ctrl+a"

[keybindings.prefix_mode]
prefix_new_window = ["c"]
prefix_split_horizontal = ["S"]
prefix_split_vertical = ["V"]
prefix_settings = ["g"]

A quieter, denser look

[appearance]
theme = "nord"
border_style = "hidden"
dockbar_position = "hidden"
shared_borders = true
whichkey_enabled = false
window_title_format = "{index}: {title}"
scrollback_lines = 50000
max_fps = 120

Troubleshooting

Configuration not loading

# Confirm the path TUIOS is actually reading
tuios config path

# Parse the file and print any errors, without the TUI covering them
tuios keybinds list

If keybinds list prints Using default keybindings..., your file was rejected and everything in it is being ignored.

Invalid key syntax

Common mistakes:

  • "cmd+t" and "super+t" - not supported
  • "opt+1" on Linux - opt is macOS only
  • "ctrl+" - incomplete combination

A setting changed nothing

Work through, in order:

  1. Is it in the reload subset? Most keys are not. See Applying changes. Restart before concluding anything.
  2. Did the file parse? A reload that fails is silent. Run tuios keybinds list.
  3. Is it a keybinding under the leader key? Those work now, but only on a current build.
  4. Is it , in window mode? That key is claimed by the settings page and cannot be rebound.
  5. Is it a hook? Check the event name spelling. Hyphens, not underscores.

On this page