Skip to content

instantWM Configuration ​

instantWM is customized through a TOML configuration file at ~/.config/instantwm/config.toml.

After editing the config file, restart instantWM, or use instantwmctl to apply some settings without restarting.

Configuration File Location ​

~/.config/instantwm/config.toml

If the file doesn't exist, instantWM uses its defaults.

Config Includes ​

You can split your configuration into multiple files using the includes directive:

toml
# config.toml (main file)
[[includes]]
file = "keybinds.toml"

[[includes]]
file = "colors.toml"

Included files are merged into the main configuration. Paths can be absolute or relative to the main config file. Circular includes are detected and prevented.

Themes ​

instantWM ships with several built-in colour themes that style the status bar, tags, window titles, borders and close buttons together. Set the top-level theme key to pick one:

toml
theme = "nord"

The theme supplies the entire [colors] table, with every colour derived from the theme's palette. Themes are resolved when the config is read, so run instantwmctl reload (or restart instantWM) after changing theme.

Built-in themes ​

NameDescription
classicThe classic instantOS look: dark background with blue, green, yellow and red accents.
catppuccin-latteLight pastel theme (Catppuccin Latte).
catppuccin-frappeDark, muted theme (Catppuccin Frappé).
catppuccin-macchiatoDark theme (Catppuccin Macchiato).
catppuccin-mochaDark theme (Catppuccin Mocha). This is the default when no theme is set.
nordDark, cool-blue theme (Nord).
gruvboxDark, warm earth-tone theme (Gruvbox).

Overriding theme colours ​

A theme is only the base. Anything you set under [colors] is merged on top of the selected theme, so you can keep a theme and tweak individual colours rather than redefining the whole palette:

toml
theme = "catppuccin-mocha"

# Keep the whole Mocha palette, but make the focused tiled-window
# border pink instead of the theme's blue.
[colors.border]
tile_focus = "#f5c2e7"

See Color Schemes for every key you can override. Because a theme resolves to the same [colors] structure, you can also keep a theme choice (and any overrides) in its own file and pull it in with includes.

INFO

If the theme name is unknown, instantWM prints a warning and falls back to the default theme (catppuccin-mocha). The rest of your config still loads normally.

Switching themes at runtime ​

You can switch themes on the running WM without editing the config:

bash
instantwmctl theme           # print the active theme
instantwmctl theme nord      # switch to a theme
instantwmctl theme --list    # list available themes

The bar, borders and tags recolour immediately. The change is not saved: instantwmctl reload reverts to whatever theme is set to in config.toml. To make a theme permanent, set theme = "..." in the config.

Full Configuration Example ​

toml
# Fonts - separate families and sizes for text and icons
[fonts]
text_family = "Inter"
text_size = 12.0
icon_family = "Symbols Nerd Font"
icon_size = 16.0

# Color configuration
[colors.tag.normal]
inactive = { fg = "#DFDFDF", bg = "#121212", detail = "#121212" }
filled = { fg = "#DFDFDF", bg = "#384252", detail = "#89B3F7" }
focus = { fg = "#121212", bg = "#89B3F7", detail = "#89B3F7" }
nofocus = { fg = "#DFDFDF", bg = "#292F3A", detail = "#3E485B" }
empty = { fg = "#5E6572", bg = "#121212", detail = "#121212" }

[colors.tag.hover]
inactive = { fg = "#DFDFDF", bg = "#1E2229", detail = "#1E2229" }
filled = { fg = "#DFDFDF", bg = "#4A5568", detail = "#A7BDD9" }
focus = { fg = "#121212", bg = "#A7BDD9", detail = "#A7BDD9" }
nofocus = { fg = "#DFDFDF", bg = "#353D4B", detail = "#475166" }
empty = { fg = "#6E7889", bg = "#1E2229", detail = "#1E2229" }

[colors.window.normal]
focus = { fg = "#DFDFDF", bg = "#292F3A", detail = "#3E485B" }
normal = { fg = "#6E7889", bg = "#292F3A", detail = "#3E485B" }
minimized = { fg = "#6E7889", bg = "#121212", detail = "#121212" }
sticky = { fg = "#F9D71C", bg = "#292F3A", detail = "#3E485B" }
sticky_focus = { fg = "#121212", bg = "#F9D71C", detail = "#F9D71C" }
edge_scratchpad = { fg = "#89B3F7", bg = "#292F3A", detail = "#3E485B" }
edge_scratchpad_focus = { fg = "#121212", bg = "#89B3F7", detail = "#89B3F7" }

[colors.window.hover]
focus = { fg = "#DFDFDF", bg = "#353D4B", detail = "#475166" }
normal = { fg = "#7E8899", bg = "#353D4B", detail = "#475166" }
minimized = { fg = "#7E7889", bg = "#1E2229", detail = "#1E2229" }
sticky = { fg = "#F9D71C", bg = "#353D4B", detail = "#475166" }
sticky_focus = { fg = "#121212", bg = "#F9D71C", detail = "#F9D71C" }
edge_scratchpad = { fg = "#89B3F7", bg = "#353D4B", detail = "#475166" }
edge_scratchpad_focus = { fg = "#121212", bg = "#89B3F7", detail = "#89B3F7" }

[colors.close_button.normal]
normal = { fg = "#6E7889", bg = "#292F3A", detail = "#3E485B" }
locked = { fg = "#6E7889", bg = "#292F3A", detail = "#3E485B" }
fullscreen = { fg = "#F9D71C", bg = "#292F3A", detail = "#3E485B" }

[colors.close_button.hover]
normal = { fg = "#DFDFDF", bg = "#81C995", detail = "#5EA984" }
locked = { fg = "#DFDFDF", bg = "#E16A98", detail = "#B7416E" }
fullscreen = { fg = "#121212", bg = "#F9D71C", detail = "#D4A61A" }

[colors.border]
normal = "#384252"
tile_focus = "#89B3F7"
float_focus = "#81C995"
snap = "#FDD663"

[colors.status]
fg = "#DFDFDF"
bg = "#121212"
detail = "#3E485B"

# Keyboard layout configuration
[keyboard]
layouts = [
  { name = "us" },
  { name = "de", variant = "nodeadkeys" }
]
options = "compose:ralt"

# Input configuration (touchpad, mouse, etc.)
[input]
# Example: enable tap-to-click on touchpads
# [input."type:touchpad"]
# tap = "enabled"
# natural_scroll = "enabled"

# Custom keybinds
[[keybinds]]
modifiers = ["Super"]
key = "Return"
action = { spawn = ["alacritty"] }

[[keybinds]]
modifiers = ["Super", "Shift"]
key = "q"
action = "kill"

[[keybinds]]
modifiers = ["Super"]
key = "F3"
action = "next_keyboard_layout"

# Desktop keybinds (work without a focused window)
[[desktop_keybinds]]
modifiers = ["Super"]
key = "d"
action = { spawn = ["instantmenu"] }

Color Schemes ​

Each color property has normal and hover variants that switch when hovered with the mouse.

Color Types ​

Each element has three colors:

  • fg (foreground): text color
  • bg (background): behind the text
  • detail: shading details below the element

Element Types ​

ElementDescription
tagTag number indicator
windowWindow title
close_buttonClose button on active window title
borderWindow border colors
statusStatus bar text

Color States ​

Tags:

  • inactive: Tag not selected, no windows
  • filled: Tag has windows but none focused
  • focus: Tag selected and has focused window
  • nofocus: Tag not selected but has windows
  • empty: Tag selected but no windows

Windows:

  • focus: Currently focused window
  • normal: Regular unfocused window
  • minimized: Minimized window
  • sticky: Sticky window (visible on all tags)
  • sticky_focus: Sticky window that is focused
  • edge_scratchpad: Window assigned to the edge overlay
  • edge_scratchpad_focus: Focused edge-overlay window

Close Button:

  • normal: Default close button
  • locked: Locked window close button
  • fullscreen: Fullscreen window close button

Border:

  • normal: Unfocused window border
  • tile_focus: Focused tiled window border
  • float_focus: Focused floating window border
  • snap: Snapped window border

Fonts ​

The [fonts] section configures the text and icon fonts used by the status bar. Text and icons have separate families and sizes (logical pixels):

toml
[fonts]
text_family = "Inter"             # font family for bar text
text_size = 12.0                  # bar text size in logical pixels
icon_family = "Symbols Nerd Font" # font family for icons (Nerd Fonts glyphs)
icon_size = 16.0                  # icon size in logical pixels
SettingTypeDefaultDescription
text_familystring"Inter"Font family used for bar text
text_sizefloat12.0Bar text size in logical pixels
icon_familystring"Symbols Nerd Font"Font family used for icons
icon_sizefloat16.0Icon size in logical pixels

Both families must be non-empty and both sizes positive. The older list form (fonts = ["Inter:size=12", ...]) is no longer accepted. When bar.height is 0 (the default), the bar height is derived from these font metrics.

Keyboard Configuration ​

The [keyboard] section configures XKB keyboard layouts:

toml
[keyboard]
layouts = [
  { name = "us" },
  { name = "de", variant = "nodeadkeys" },
  { name = "fr" }
]
options = "compose:ralt"          # XKB options
model = "pc105"                   # Keyboard model (optional)
swapescape = false                # Swap Caps Lock and Escape

Set swapescape = true to swap Caps Lock and Escape without having to spell out the XKB option string yourself.

Input Configuration ​

Configure touchpad and mouse settings:

toml
# Global settings
[input]
[input."type:touchpad"]
tap = "enabled"
natural_scroll = "enabled"
accel_profile = "adaptive"
pointer_accel = 0.5

[input."type:mouse"]
pointer_accel = 0.3

Valid values:

  • tap: "enabled" or "disabled" (the CLI spellings "on" / "off" are also accepted)
  • natural_scroll: "enabled" or "disabled" ("on" / "off" also accepted)
  • accel_profile: "flat" or "adaptive"
  • pointer_accel: Floating point number
  • scroll_factor: Floating point multiplier applied to scroll events (defaults to 1.0 when unset)
  • left_handed: "enabled" or "disabled" ("on" / "off" also accepted); swaps the primary/secondary buttons for left-handed use
  • map_to_output: Output name (e.g. "eDP-1") that receives absolute events from this device, such as a drawing tablet; use "*" to map the device across the complete active output layout

Layout tree and gaps ​

The layout section controls spacing and interaction with instantWM's persistent manual tree:

toml
[layout]
inner_gap = 8
outer_gap = 8
smart_gaps = true
maximized_gaps = false
keyboard_resize_step = 0.05
minimum_weight = 0.15
pointer_edge_fraction = 0.34
new_window_placement = "auto-resize"
SettingTypeDefaultDescription
inner_gapinteger0Spacing between tiled windows (logical pixels)
outer_gapinteger0Spacing between tiled windows and the monitor edge (logical pixels)
smart_gapsbooleantrueDisable all gaps when only one or zero tiled windows are present. Has no visible effect when both gaps are 0 (the default).
maximized_gapsbooleanfalseApply configured gaps during maximized presentation
keyboard_resize_stepfloat0.05Fraction of an axis transferred by one tree resize command
minimum_weightfloat0.15Preferred minimum weight of a child in a split run
pointer_edge_fractionfloat0.34Fraction of a target occupied by pointer placement edge bands
new_window_placementstring"auto-resize"How a new window joins the tiling tree (see below)

new_window_placement controls where a window that is not yet in a tag's persistent tiling tree gets inserted:

  • "auto-resize" (default): place the newcomer automatically and resize the existing tree to give it room.
  • "auto": split the best existing leaf without deliberately rebalancing the rest of the tree.
  • "force": give the first newcomer a leading half of a new vertical root split; consecutive untouched insertions adapt that region into balanced rows or columns, and any manual tree edit starts a new sequence.

Inner gaps are split evenly between adjacent windows. Outer gaps shrink the layout area inward from all four edges. Both values are clamped to a minimum of 0. Gaps do not apply to floating windows.

Layout presets such as Grid rewrite the tree once. The settings above apply to the manual edits you make afterwards; see Layouts.

Floating windows and click-to-raise ​

toml
# Raise a floating window to the top of the stack when its client area is
# left-clicked. Disabled by default so click-to-focus and focus-follows-mouse
# do not disturb the explicit floating-window stacking order.
raise_floating_on_click = false

With the default (false), clicking inside a floating window focuses it without changing the stacking order, which keeps manually arranged floating windows where you put them. Set it to true if you prefer each click to also bring the window to the front.

The setting also lives as raise_floating_on_click inside the [window] section; either location enables it.

Window behaviour ​

The [window] section controls border width, snapping and how instantWM honours client-provided window hints:

toml
[window]
border_width_px = 3     # WM border width in pixels
snap_threshold = 32     # snap distance while dragging (pixels)
resize_hints = true     # respect client size hints
decor_hints = true      # honour client decoration requests (X11)
SettingTypeDefaultDescription
border_width_pxinteger3Width of the WM border drawn around managed windows. Must be non-negative
snap_thresholdinteger32Distance in pixels within which a dragged window snaps to screen edges and other windows. Must be non-negative
resize_hintsbooleantrueRespect clients' size hints when resizing; terminals, for example, then resize in whole rows and columns instead of arbitrary pixels
decor_hintsbooleantrueX11 only: honour _MOTIF_WM_HINTS decoration requests. When a client asks to be drawn without border or title (some games and toolkits do), instantWM draws no border for it. Set to false to always draw the configured border regardless of what the client requests. On Wayland, decoration negotiation happens through the xdg-decoration protocol instead and is not affected by this key
focus_follows_mousestring"normal"Pointer-focus policy: "off" never moves keyboard focus with the pointer, "normal" moves it on physical pointer motion, and "force" also moves focus when a scene change puts a different window under the pointer. instantwmctl config set window.focus_follows_mouse <mode> overrides this for the session; reload restores the configured value
focus_follows_float_mousebooleantrueWhether hover focus also applies to floating windows while a tiling layout is active. Flip it for the session with instantwmctl config toggle window.focus_follows_float_mouse
raise_floating_on_clickbooleanfalseSame as the top-level raise_floating_on_click key; either location enables it

Every key can be read and changed at runtime with instantwmctl config get window.<key> / instantwmctl config set window.<key> <value>, and booleans can be flipped in one step with instantwmctl config toggle window.<key>. As with other config set calls, the change applies immediately but is not persisted; put the value in config.toml to keep it across reloads and restarts.

Animations ​

toml
[animations]
enabled = true
# 1.0 = designed speed; 0.5 = half speed (durations doubled);
# 2.0 = twice as fast (durations halved)
speed = 1.0
SettingTypeDefaultDescription
enabledbooleantrueMaster switch for window animations. false disables them entirely
speedfloat1.0Global animation speed multiplier; valid range 0.01–100.0

Below 1.0 slows animations down, above 1.0 speeds them up. Durations are scaled, so a factor of 2.0 halves every animation's duration rather than skipping frames.

enabled is the persistent setting; super+shift+alt+s (bound to the config_toggle action) or instantwmctl config toggle animations.enabled flips it for the current session, and instantwmctl reload restores the configured value.

The speed and switch can also be changed at runtime:

bash
instantwmctl config get animations.speed
instantwmctl config set animations.speed 1.5
instantwmctl config get animations.enabled
instantwmctl config toggle animations.enabled   # prints the new value

instantwmctl config set applies immediately but is not saved. Put the value in config.toml for a permanent setting.

Custom Keybinds ​

Add or override keybinds:

toml
# Spawn a command
[[keybinds]]
modifiers = ["Super"]
key = "Return"
action = { spawn = ["alacritty"] }

# Named actions
[[keybinds]]
modifiers = ["Super", "Shift"]
key = "q"
action = "kill"

# Remove a default binding
[[keybinds]]
modifiers = ["Super"]
key = "f"
action = "none"

# Apply a tree preset
[[keybinds]]
modifiers = ["Super"]
key = "g"
action = { set_layout = "grid" }

# Adjust master window count
[[keybinds]]
modifiers = ["Super"]
key = "i"
action = { inc_master_count = 1 }

# Enter a mode
[[keybinds]]
modifiers = ["Super"]
key = "r"
action = { set_mode = "resize" }

Available Modifiers ​

  • Super or Mod4 - Windows/Super key
  • Shift - Shift key
  • Ctrl or Control - Control key
  • Alt or Mod1 - Alt key

Available actions ​

Simple actions use a string, for example action = "begin_tree_placement" or action = "toggle_tiling_maximized". The most relevant tree actions are:

  • focus_left/right/up/down
  • key_move_left/right/up/down
  • key_resize_left/right/up/down
  • tree_grow and tree_shrink
  • begin_tree_placement
  • layout_tile, layout_grid, layout_horiz_grid, layout_bottom_stack, and layout_bstack_horiz
  • layout_float, layout_maximized, and toggle_tiling_maximized
  • edge_scratchpad_create and edge_scratchpad_toggle

For the full list for your build, with descriptions and argument examples, run instantwm --list-actions or instantwmctl action --list.

Any named action with arguments can use a table containing exactly one action name. Give it a string, integer, or boolean for one argument, or an array for multiple arguments. For example: action = { spawn = ["alacritty"] }, action = { set_layout = "tile" }, action = { inc_master_count = 1 }, or action = { config_toggle = "animations.enabled" }. The action parser checks the name, argument count, and values. The array form, such as action = ["set_layout", "tile"], also works. Use action = "none" to remove a binding. For multiple actions, use action = { sequence = [{ set_layout = "tile" }, { spawn = ["alacritty"] }] }. { unbind = true } is no longer accepted; use "none" instead.

config_set and config_toggle reach any runtime config key from a keybind, so booleans and values do not need a dedicated action:

toml
[[keybinds]]
modifiers = ["Super", "Alt", "Shift", "Ctrl"]
key = "d"
action = { config_toggle = "window.decor_hints" }

[[keybinds]]
modifiers = ["Super", "Alt", "Shift", "Ctrl"]
key = "g"
action = { config_set = ["layout.inner_gap", "12"] }

See Modes for mode-local bindings and the built-in placement mode.

Window rules ​

Window rules apply placement and tag settings automatically when a window matching a given class, instance, or title appears. A window matches a rule when every criterion you supply matches; criteria you leave out match anything. class, instance, and title are case-sensitive substring matches.

toml
# Centre pavucontrol as a floating window
[[rules]]
class = "pavucontrol"
is_floating = "float_center"

# Open mpv floating on tag 3 (bit 2 → value 4)
[[rules]]
class = "mpv"
is_floating = "float"
tags = 4

# Match on the window title
[[rules]]
class = "steam"
title = "Friends List"
is_floating = "float"

# Pin a game to fullscreen on the second monitor (layout position 1)
[[rules]]
class = "complex-game"
is_floating = "float_fullscreen"
monitor = 1

# Open a mixer pinned to an exact spot on a specific output, without a border
[[rules]]
class = "pavucontrol"
is_floating = "float"
monitor = "DP-1"
geometry = { x = 100, y = 50, width = 800, height = 600 }
borderless = true
FieldDescription
classMatch the window's WM class (substring).
instanceMatch the window's WM instance (substring).
titleMatch the window title (substring).
tagsBitmask of tags to assign. Bit 0 (value 1) is tag 1, bit 1 (value 2) is tag 2, bit 2 (value 4) is tag 3, and so on. Combine tags with bitwise OR (tags 1 + 3 = 1 | 4 = 5). The bits are merged into the window's tags; 0 or unset leaves the tags unchanged.
is_floatingInitial mode: "tiled", "float", "float_center", "float_fullscreen", or "scratchpad".
monitorWhich monitor the window lands on: an output name ("DP-1"), a 0-based layout position (1), "focused", "primary", or "any" (default). Same grammar as the CLI; see Monitor selectors.
geometryExact floating placement relative to the target monitor's work area (the usable area below the bar): { x = 100, y = 50, width = 800, height = 600 }. Setting a geometry implies floating placement; the window keeps this exact spot even when monitors are rearranged, because coordinates are relative to the monitor rather than the desktop.
borderlessManage the matched window without the WM border (true/false, default false).

Only the first matching rule is applied to a window.

For a one-shot runtime analog (useful when you want a rule to apply to the next matching window and then disappear) see instantwmctl pending-tmp-rule. Pending tmp rules use the same matching and placement fields but auto-expire and are consumed on first match.

Control Commands ​

Runtime control is provided by instantwmctl. See the instantwmctl command reference for commands and examples. instantwmctl action --list prints the actions usable in keybindings.

Runtime Control ​

instantWM reads status text from the X11 root window name property (X11) or writes to the status bar directly (Wayland). Configure a status command in your config:

toml
status_command = "i3status-rs"

Or set status manually:

bash
instantwmctl update-status "My Status"

Custom modes ​

Modes use the same binding format and can invoke the tree actions above. See Modes for a full example and for customizing the built-in placement mode.

Startup commands ​

Like sway's exec / exec_once and Hyprland's exec-once, you can run commands when instantWM starts:

toml
# Run once at startup (not repeated on reload)
exec_once = ["xmobar", "wal -R"]

# Run at startup and again on every `instantwmctl reload`
exec = ["killall picom; picom"]

exec_once is only fired during the initial startup sequence; exec is also re-run on each config reload, so it suits commands that need to be kept alive or restarted when the config changes.

Hooks ​

Hooks run an action when something happens in the window manager, such as a monitor being plugged in or out. The action accepts exactly the same values as a keybind action: named actions, spawn, sequence, set_layout, set_mode, and so on.

toml
# Re-apply wallpaper etc. whenever the monitor setup changes in any way
[[hooks]]
event = "monitors_changed"
action = { spawn = ["sh", "-c", "~/.local/bin/monitors-changed.sh"] }

# Only react to one specific output being plugged in
[[hooks]]
event = "monitor_connected"
monitor = "HDMI-A-1"
action = { sequence = [{ set_layout = "tile" }, { spawn = ["notify-send", "Docked"] }] }

# Notify whenever any monitor goes away
[[hooks]]
event = "monitor_disconnected"
action = { spawn = ["notify-send", "Monitor disconnected"] }
FieldRequiredDescription
eventyesOne of the events below
monitornoOutput name to filter on (e.g. "DP-1"). Only valid for monitor_connected / monitor_disconnected. Without it the hook fires for every output.
actionyesAny keybind action

Events ​

EventFiresPer monitor?
monitor_connectedOnce for each output that appeared (plugged in or enabled)yes
monitor_disconnectedOnce for each output that disappeared (unplugged or disabled)yes
monitors_changedOnce whenever the monitor setup changed in any way: outputs added or removed, or their size, position, scale or order changedno

If you don't care what changed and just want to react to the new setup, use monitors_changed. Plugging in a dock with two screens runs it once, not twice.

Hooks do not fire for the monitors present at startup; use exec / exec_once for startup work.

WARNING

A hook that changes the output configuration itself (e.g. by running xrandr) triggers monitors_changed again and can loop forever.

Technical behavior
  • Order: when several events happen together they run as: all monitor_disconnected, then all monitor_connected, then a single monitors_changed. Hooks for the same event run in config order.
  • Detection: instantWM compares the monitor setup with the one it saw last, once per event-loop iteration and after the layout has been updated. Changes that happen together are combined, and an output that disconnects and reconnects within one iteration fires nothing.
  • Sources: changes count regardless of where they come from: physical hotplug, [monitors] settings (including enable = false) on reload, instantwmctl, output-management tools like wlr-randr, or xrandr on X11. Outputs that are physically mirrored into one monitor count as that single monitor.
  • What counts: only changes that affect the layout. Refresh rate, VRR, and rotations or flips that keep the output size (e.g. 180°) do not; 90°/270° rotations do, because they change the size. UI-only changes such as a different bar height do not count either.

Environment for spawned commands ​

Processes started by a hook (via spawn) receive these extra variables, so a single script can handle every case:

VariableExampleDescription
INSTANTWM_HOOK_EVENTmonitor_connectedThe event that fired
INSTANTWM_MONITORHDMI-A-1The output that changed (per-monitor events only)
INSTANTWM_MONITORSeDP-1 HDMI-A-1All current outputs, space separated
sh
#!/bin/sh
# ~/.local/bin/monitor-hook.sh
case "$INSTANTWM_HOOK_EVENT" in
  monitor_connected)    notify-send "Docked: $INSTANTWM_MONITOR" ;;
  monitor_disconnected) notify-send "Undocked: $INSTANTWM_MONITOR" ;;
  monitors_changed)     notify-send "Outputs: $INSTANTWM_MONITORS" ;;
esac

Validation ​

Unlike keybinds, an invalid hook makes the whole config fail to load, and the error names the offending entry, e.g. hooks[1] (monitor_connected): unknown action 'foo'.

What counts as invalid

An unknown event, a misspelled field, an unknown action, "none", a missing argument (such as action = { spawn = [] }), or a monitor filter on monitors_changed. On reload the previous config stays active; at startup the built-in defaults are used.

Monitor Configuration ​

Configure specific monitor settings:

toml
[monitors."DP-1"]
resolution = "1920x1080"
refresh_rate = 144.0
position = "0,0"
scale = 1.0
enable = true
transform = "normal"      # rotation / reflection
vrr = "auto"              # variable refresh rate policy
# Tag display, overridden for this output only (see [Status bar](#status-bar)):
show_empty_tags = false   # hide tags without windows on this display
tag_slots = 5             # fewer tag cells on this display

# To mirror another output instead, set mirror = "DP-1" on the mirror head.
# mirror_fit = "contain"   # Wayland: contain (bars) or cover (crop)

[monitors."HDMI-A-1"]
position = "left-of:DP-1"

# Defaults for every output without its own entry:
[monitors."*"]
show_empty_tags = true
tag_slots = 7

Position can be specified as:

  • Absolute: "X,Y" (e.g., "1920,0")
  • Relative: "left-of:OUTPUT", "right-of:OUTPUT", "above:OUTPUT", "below:OUTPUT"

transform rotates or mirrors the output. Valid values are "normal", "90", "180", "270", "flipped", "flipped-90", "flipped-180", and "flipped-270".

vrr controls variable refresh rate (FreeSync / G-Sync) and accepts "off" (default), "auto" (let the driver decide), or "on".

Set mirror = "DP-1" on a second output to show the source output's content. The pair acts as one logical monitor. The mirror's position and scale are ignored. On Wayland, its own resolution, refresh rate, and transform still control scanout; mirror_fit = "contain" (default) adds bars when aspect ratios differ, while "cover" crops. X11 uses the source's mode and cannot scale the mirror. A disconnected or disabled source leaves the other output independent until the source returns. Use mirror = "none" to clear the setting.

show_empty_tags and tag_slots are per-output display settings: each resolves as [monitors."<name>"] → [monitors."*"] → the [bar] default, field by field, so an entry that sets one of them keeps inheriting the other. They apply on startup and reload, and can be changed at runtime with instantwmctl monitor set DP-1 --show-empty-tags false --tag-slots 5 or instantwmctl config set monitors.DP-1.tag_slots 5 (which takes effect immediately, without a reload).

Status bar ​

The [bar] section controls the visibility and geometry of the status bar:

toml
[bar]
show = true             # show the top status bar
show_bottom = false     # show the bottom gesture strip
show_empty_tags = true  # show tags that hold no windows
tag_slots = 9           # number of tag cells in the bar
height = 0              # bar height in logical pixels; 0 = derive from fonts
startmenu_size = 30     # width of the start-menu hit target in logical pixels
SettingTypeDefaultDescription
showbooleantrueShow the top status bar. super+b hides it on the current tag view only (a session override, cleared by reload or config set bar.show)
show_bottombooleanfalseShow the bottom gesture strip (a plain background with no contents). Also toggled at runtime with super+shift+b or instantwmctl config toggle bar.show_bottom
show_empty_tagsbooleantrueShow tags that hold no windows and are not selected. Can be overridden per output (see Monitor configuration); super+ctrl+shift+s (toggle_hide_tags) overrides it on the selected monitor for the session, and reload restores the configured value
tag_slotsinteger9Number of tag cells in the bar, 1–21. Outputs with fewer tags show all of them; when the tag set is wider, the last cell shows the current tag instead of a fixed index (the classic dwm overflow cell). Fewer cells suit wordy tag names, more suit icon labels. Can be overridden per output
heightinteger0Bar height in logical pixels. 0 derives the height from the configured fonts. Must be non-negative
startmenu_sizeinteger30Width of the start-menu hit target in logical pixels

height and startmenu_size must be non-negative; tag_slots must be between 1 and 21.

All of these are runtime-editable, and booleans have a one-step flip:

bash
instantwmctl config toggle bar.show_empty_tags   # prints the new value
instantwmctl config set bar.tag_slots 5

config set bar.* re-applies the bar immediately, including dropping any per-view bar overrides from super+b.

System tray ​

The [systray] section controls the tray icons shown in the status bar:

toml
[systray]
show = true            # show tray icons in the bar
pinning = 0            # monitor the tray lives on; 0 = follow the selected monitor
spacing = 0            # extra padding around tray icons in logical pixels
menu_backend = "auto"  # how a tray icon's context menu is presented
SettingTypeDefaultDescription
showbooleantrueShow system tray icons in the status bar
pinninginteger00 keeps the tray on the currently selected monitor; any other value pins it to that 1-based layout position (falling back to the first monitor when fewer are connected)
spacinginteger0Extra spacing around each tray icon in logical pixels
menu_backendstring"auto"How a tray icon's context menu is presented: "auto" uses instantMENU when available and falls back to the bar, "statusbar" always renders the menu inline in the bar, and "instantmenu" always delegates to an external instantMENU process

Tags ​

The [tags] section defines the tag set itself and how the bar labels it:

toml
[tags]
# One entry per tag — the list length is the number of tags (max 21).
# The last entry is the scratchpad tag.
names = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "s"]
# Optional nerd-font glyphs, positionally matched to `names`.
# A shorter list leaves the remaining tags without an icon; an empty
# string always means "no icon".
icons = ["", "", "", "", "", "", "", "", ""]
# Show the icons instead of the names (++super+alt+s++ flips this).
show_icons = false
SettingTypeDefaultDescription
nameslist of strings"1" … "20", "s"Label per tag; the list length is the number of tags. At most 21 entries, each 1–16 bytes and non-empty
iconslist of stringsemptyIcon label per tag, positionally matched to names. Shown instead of the name while show_icons is on. May be shorter than names (the rest get no icon); longer is rejected
show_iconsbooleanfalseShow the icons in the tag bar instead of the names. Flip for the session with super+alt+s or instantwmctl config toggle tags.show_icons; reload restores the configured value

Because tag icons usually need a symbol font, point [fonts] icon_family at one (for example "Symbols Nerd Font" or "Font Awesome 6 Free").

names and icons define the tag set, so they take effect on startup and reload only — config set tags.names … is rejected and says so. To relabel a tag for the current session instead:

bash
instantwmctl tag name "web"   # rename the tags in the current view
instantwmctl tag list         # names, icons, the active label, occupancy
instantwmctl tag reset        # drop session renames, back to config

Cursor (Wayland) ​

Cursor (Wayland) ​

On the Wayland backend, the cursor theme and size are taken from the [cursor] section (this has no effect on X11):

toml
[cursor]
theme = "Adwaita"   # xcursor theme name
size = 24           # cursor size in logical pixels

These mirror the XCURSOR_THEME and XCURSOR_SIZE environment variables; the config values take precedence. Reload the config with instantwmctl reload to apply a change.

Environment variables ​

Most behavior is configured in config.toml. The variables below are read at launch (restart instantWM to apply) or are set by instantWM for child processes. config.toml wins when both are present.

Backend selection (read at startup) ​

VariableEffectDefault / fallback
WAYLAND_DISPLAYIf set, instantwm defaults to the nested Wayland backend (wayland-nested) instead of inspecting DISPLAY.Unset on a bare tty.
DISPLAYIf WAYLAND_DISPLAY is unset and DISPLAY is set, the X11 backend is selected. Also the X11 client display string (:0, :1, …). On Wayland, instantWM's XWayland sets DISPLAY=:N for children.Unset on Wayland-only / DRM.
--backend (CLI)instantwm --backend x11|nested|drm overrides the auto-detection above.Auto-detect.

On a bare tty with neither variable set, instantWM selects the standalone DRM/KMS backend.

Logging ​

VariableValuesDefault
INSTANTWM_LOGoff, error, warn, info, debug, trace (case-insensitive). Unknown value is ignored.warn. Set before launch, e.g. INSTANTWM_LOG=debug instantwm.

IPC socket ​

VariableWho reads itEffect
INSTANTWM_SOCKETinstantwmctl (and any IPC client)Path to the compositor socket. Default /tmp/instantwm-<uid>.sock with -<n> suffix on collision. The compositor publishes the bound path here for its children.
INSTANTWM_SOCKET_BINDinstantwm server at bind timeExact path the compositor must bind (no suffix fallback). Stale socket is removed if nothing is listening. Consumed at startup and removed from the environment before spawning children, so tests/nested sessions cannot silently fall back to another compositor's socket. Used by tests/e2e.sh and nested runs.

Startup ​

VariableEffect
INSTANTWM_AUTOSTART0 skips ins autostart (the distro autostart hook). 1 (default) runs it once at startup. Also respected by scripts/startinstantos.
INSTANTWM_TEST1 enables the unstable instantwmctl test … namespace (test wait, test window …, test pointer …). Without it those commands fail with test commands are disabled. Not a stable user API.
DBUS_SESSION_BUS_ADDRESSIf already set, instantWM reuses the session bus. If unset on Wayland, instantWM forks dbus-daemon --session --fork --print-address=1 and sets it, then imports WAYLAND_DISPLAY/XDG_* into dbus-update-activation-environment --systemd for portals.

Keyboard and cursor fallbacks (read when config.toml omits them) ​

These are standard freedesktop variables. config.toml takes precedence when set.

VariableFallback forExample
XKB_DEFAULT_LAYOUT[keyboard].layouts when the list is empty. Empty string → us.de
XKB_DEFAULT_VARIANTSingle-layout variantnodeadkeys
XKB_DEFAULT_OPTIONS[keyboard].options when unsetcompose:ralt
XKB_DEFAULT_MODEL[keyboard].model when unsetpc105
XCURSOR_THEME[cursor].theme on Wayland DRMAdwaita
XCURSOR_SIZE[cursor].size on Wayland DRM (non-negative integer)24

Variables set by instantWM for children ​

Do not set these manually; instantWM overwrites them at startup for toolkit and script detection:

  • INSTANTWM=1: generic "inside instantWM" flag.
  • INSTANTWM_BACKEND=x11 | wayland-nested | wayland-drm: selected backend. instantWM checks it so that systemctl --user stop/start of xdg-desktop-portal* and instantwm-session.target only runs on DRM.
  • INSTANTWM_SOCKET: published IPC path (see above).
  • On Wayland, instantWM also exports WAYLAND_DISPLAY, XDG_SESSION_TYPE=wayland, XDG_CURRENT_DESKTOP=instantwm, XDG_SESSION_DESKTOP=instantwm, DESKTOP_SESSION=instantwm, unsets DISPLAY initially, and sets GDK_BACKEND=wayland, QT_QPA_PLATFORM=wayland, SDL_VIDEODRIVER=wayland, CLUTTER_BACKEND=wayland. XWayland then sets DISPLAY=:N.

Check the current export with printenv | grep -E '^INSTANTWM|^WAYLAND_DISPLAY|^DISPLAY' inside a terminal launched from instantWM.