name: niri os: lnx description: "Use for niri Wayland compositor configuration and IPC." license: MIT
niri — Wayland compositor config editor
niri is a scrollable tiling Wayland compositor configured via KDL at
~/.config/niri/config.kdl.
Config file basics
- Location:
~/.config/niri/config.kdl - Syntax: KDL — similar to TOML but with slashes
- Live-reload: Save and changes apply immediately
- Validation:
Run
niri validateto check for errors before restarting - Default config: If no config exists, niri creates one from the embedded default at build time
- Include files:
Use
include "file.kdl"to split config across files (since 25.11)
Config sections quick reference
| Section | Controls | Docs |
|---|---|---|
input {} |
Keyboard layout, repeat, numlock; touchpad/mouse/trackpoint/trackball/tablet/touch settings; focus-follows-mouse, mod-key | Input |
output "name" {} |
Per-monitor: mode, scale, transform, position, VRR, background, hot-corners, layout overrides | Outputs |
binds {} |
Key bindings, scroll bindings, mouse click bindings | Key Bindings |
switch-events {} |
Lid open/close, tablet mode switch bindings | Switch Events |
layout {} |
Gaps, column widths, focus ring, border, shadow, tab-indicator, struts, background | Layout |
workspace "name" {} |
Named workspaces (always exist, can bind to outputs) | Named Workspaces |
window-rule {} |
Per-window matching and properties (opacity, floating, size, VRR, etc.) | Window Rules |
layer-rule {} |
Per-layer-shell-surface matching and properties (opacity, shadow, radius) | Layer Rules |
animations {} |
Animation curves/duration for workspace-switch, window-open/close, resize, overview | Animations |
gestures {} |
DnD edge scroll, workspace switch, hot-corners | Gestures |
recent-windows {} |
Alt-Tab switcher: debounce, highlights, previews, binds | Recent Windows |
debug {} |
Debug flags, DRM device override, tracing | Debug Options |
| Top-level flags | spawn-at-startup, prefer-no-csd, screenshot-path, cursor, overview, xwayland-satellite, clipboard, hotkey-overlay, config-notification |
Miscellaneous |
KDL syntax basics
// Single-line comment
/- multi-line comment (comments out entire section) -/
// Flags (enable by writing, disable by commenting out)
input {
focus-follows-mouse
// warp-mouse-to-focus // disabled
}
// Sections (most can only appear once)
input {
keyboard {
xkb {
layout "us"
variant "colemak_dh_ortho"
options "compose:ralt,ctrl:nocaps"
}
repeat-delay 600
repeat-rate 25
numlock
}
touchpad {
tap
natural-scroll
accel-speed 0.2
}
}
// Repeating sections (by device/output name)
output "eDP-1" {
scale 2.0
mode "1920x1080@120.030"
}
output "HDMI-A-1" {
mode "2560x1440@143.912"
}
// Colors
focus-ring {
active-color "#7fc8ff"
inactive-color "#505050"
urgent-color "#9b0000"
active-gradient from="#80c8ff" to="#bbddff" angle=45
}
// Proportions vs fixed
preset-column-widths {
proportion 0.33333
proportion 0.5
fixed 1280
}
default-column-width { proportion 0.5; }
// Regex strings
window-rule {
match app-id=r#"^org\.telegram\.desktop$"#
match title="^Picture-in-Picture$"
}
Important rules
Do not execute niri-session for inspection or testing; inspect its source instead.
It ignores --help and can terminate the active desktop session.
Execute it only when the user explicitly requests session startup.
binds {}has NO defaults — if you include it, you must provide all bindings you need. Copy from the default config first.- Most sections cannot repeat — only
output "name" {},workspace "name" {},window-rule {}, andlayer-rule {}can repeat. - Window rules are ordered — earlier rules match first and override later ones.
- Most settings merge — when included files set partial sections, only those properties change; other properties keep their values.
strutsis non-merging — writingstruts {}in an included file completely replaces (doesn't merge with) a priorstruts {}.
Common patterns
Change keybindings
binds {
// Mod is Super on TTY, Alt in nested/winit mode
Mod+T { spawn "alacritty"; }
Mod+D { spawn "fuzzel"; }
Mod+Shift+E { quit; }
Mod+Q { close-window; }
Mod+Left { focus-column-left; }
Mod+Right { focus-column-right; }
Mod+J { focus-window-down; }
Mod+K { focus-window-up; }
Mod+Shift+Left { move-column-left; }
Mod+Shift+Right { move-column-right; }
Mod+U { focus-workspace-down; }
Mod+I { focus-workspace-up; }
Mod+Shift+U { move-workspace-down; }
Mod+Shift+I { move-workspace-up; }
Mod+Shift+F { toggle-fullscreen; }
Mod+V { move-window-between-floating-and-tiling; }
Mod+F { maximize-column; }
Mod+R { switch-preset-column-width; }
Mod+Shift+R { switch-preset-window-height; }
Mod+- { resize-column-width -10; }
Mod+= { resize-column-width +10; }
Mod+Tab { recent-windows-cycle; }
Mod+Shift+Tab { recent-windows-cycle reverse; }
}
Configure outputs
// Built-in laptop screen
output "eDP-1" {
scale 2.0
}
// External monitor with high refresh
output "HDMI-A-1" {
mode "2560x1440@143.912"
position x=1920 y=0
variable-refresh-rate
}
// Rotate a monitor
output "DP-1" {
transform "90"
}
// Disable a monitor
output "HDMI-A-2" {
off
}
Window rules
// Open Firefox maximized
window-rule {
match app-id="firefox$"
open-maximized true
}
// Firefox PiP floating at bottom-left
window-rule {
match app-id="firefox$" title="^Picture-in-Picture$"
open-floating true
default-floating-position x=32 y=32 relative-to="bottom-left"
default-column-width { fixed 480; }
default-window-height { fixed 270; }
}
// Make Telegram floating except media viewer
window-rule {
match app-id=r#"^org\.telegram\.desktop$"#
exclude title="^Media viewer$"
open-floating true
}
// Block sensitive windows from screencasts
window-rule {
match app-id=r#"^org\.keepassxc\.KeePassXC$"#
block-out-from "screencast"
}
// App-specific column width
window-rule {
match app-id="^blender$"
default-column-width { fixed 1200; }
}
// Screencasted windows get red border
window-rule {
match is-window-cast-target=true
focus-ring { active-color "#f38ba8"; inactive-color "#7d0d2d"; }
border { inactive-color "#7d0d2d"; }
}
// Default floating position for a "dropdown terminal"
window-rule {
match app-id="^dropdown$"
open-floating true
default-floating-position x=0 y=0 relative-to="top"
default-window-height { proportion 0.5; }
default-column-width { proportion 0.8; }
}
Layout: gaps, border, focus ring, shadow
layout {
gaps 16
default-column-width { proportion 0.5; }
preset-column-widths {
proportion 0.2
proportion 0.33333
proportion 0.5
proportion 0.66667
proportion 0.8
}
center-focused-column "on-overflow"
always-center-single-column
empty-workspace-above-first
focus-ring {
on
width 4
active-color "#7fc8ff"
inactive-color "#505050"
urgent-color "#9b0000"
}
border {
off
// width 4
// active-color "#ffc87f"
// inactive-color "#505050"
}
shadow {
on
softness 30
spread 5
offset x=0 y=5
draw-behind-window true
color "#00000070"
}
tab-indicator {
on
hide-when-single-tab
place-within-column
gap 5
width 4
length total-proportion=0.5
position "right"
}
insert-hint {
on
color "#ffc87f80"
}
struts {
left 64
right 64
top 64
bottom 64
}
}
Input: keyboard, touchpad
input {
keyboard {
xkb {
layout "us"
variant "colemak_dh_ortho"
options "compose:ralt,ctrl:nocaps"
}
repeat-delay 600
repeat-rate 25
track-layout "global"
numlock
}
touchpad {
tap
dwt
natural-scroll
accel-speed 0.2
accel-profile "flat"
click-method "clickfinger"
scroll-method "two-finger"
}
mouse {
natural-scroll
accel-speed 0.2
}
focus-follows-mouse
warp-mouse-to-focus
workspace-auto-back-and-forth
}
mod-key "Super"
mod-key-nested "Alt"
Animations
animations {
workspace-switch {
spring damping-ratio=1.0 stiffness=1000 epsilon=0.0001
}
window-open {
duration-ms 150
curve "ease-out-expo"
}
window-close {
duration-ms 150
curve "ease-out-quad"
}
horizontal-view-movement {
spring damping-ratio=1.0 stiffness=800 epsilon=0.0001
}
window-movement {
spring damping-ratio=1.0 stiffness=800 epsilon=0.0001
}
window-resize {
spring damping-ratio=1.0 stiffness=800 epsilon=0.0001
}
overview-open-close {
spring damping-ratio=1.0 stiffness=800 epsilon=0.0001
}
}
Named workspaces with layout overrides
workspace "browser"
workspace "chat" {
open-on-output "HDMI-A-1"
}
workspace "code" {
layout {
gaps 32
border {
on
width 4
}
struts {
left 64
right 64
bottom 64
top 64
}
}
}
Spawn at startup
spawn-at-startup "waybar"
spawn-at-startup "alacritty"
spawn-sh-at-startup "mate-polkit &"
Layer rules (for waybar, fuzzel, etc.)
layer-rule {
match namespace="waybar"
// waybar opacity
}
layer-rule {
match namespace="^launcher$"
opacity 0.95
shadow { on; }
geometry-corner-radius 10
}
layer-rule {
match namespace="^notifications$"
block-out-from "screencast"
}
Gestures
gestures {
dnd-edge-view-scroll {
trigger-width 30
delay-ms 100
max-speed 1500
}
dnd-edge-workspace-switch {
trigger-height 50
delay-ms 100
max-speed 1500
}
hot-corners {
top-left
// top-right
// bottom-left
// bottom-right
}
}
Recent windows (Alt-Tab switcher)
recent-windows {
debounce-ms 750
open-delay-ms 150
highlight {
active-color "#999999ff"
urgent-color "#ff9999ff"
padding 30
corner-radius 0
}
previews {
max-height 480
max-scale 0.5
}
binds {
Alt+Tab { next-window; }
Alt+Shift+Tab { previous-window; }
Alt+grave { next-window filter="app-id"; }
Alt+Shift+grave { previous-window filter="app-id"; }
}
}
Miscellaneous settings
prefer-no-csd
screenshot-path "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png"
cursor {
xcursor-theme "breeze_cursors"
xcursor-size 48
hide-when-typing
hide-after-inactive-ms 1000
}
overview {
zoom 0.5
backdrop-color "#262626"
workspace-shadow {
softness 40
spread 10
offset x=0 y=10
color "#00000050"
}
}
xwayland-satellite {
// off
path "xwayland-satellite"
}
clipboard {
disable-primary
}
hotkey-overlay {
skip-at-startup
hide-not-bound
}
config-notification {
disable-failed
}
niri msg IPC commands
Communicate with a running niri instance via niri msg.
Socket at $NIRI_SOCKET.
Query state
niri msg outputs # List outputs with modes, VRR support
niri msg windows # List all windows
niri msg workspaces # List workspaces
niri msg focused-window # Focused window info
niri msg focused-output # Focused output name
niri msg layers # List layer-shell surfaces
niri msg pick-window # Click window → print app-id and title
niri msg --json outputs # JSON output for scripting
Actions
niri msg action focus-workspace <index-or-name>
niri msg action focus-workspace-up
niri msg action focus-workspace-down
niri msg action move-workspace-to-top
niri msg action move-column-to-workspace <workspace-name>
niri msg action move-window-to-workspace <workspace-name>
niri msg action focus-column-left
niri msg action focus-column-right
niri msg action focus-column-first
niri msg action focus-column-last
niri msg action focus-window-down
niri msg action focus-window-up
niri msg action move-column-left
niri msg action move-column-right
niri msg action move-window-down
niri msg action move-window-up
niri msg action switch-preset-column-width
niri msg action switch-preset-window-height
niri msg action set-column-width <+/-pixels>
niri msg action set-window-height <+/-pixels>
niri msg action maximize-column
niri msg action center-column
niri msg action resize-column-width <+/-percentage>
niri msg action resize-window-height <+/-percentage>
niri msg action toggle-fullscreen
niri msg action maximize
niri msg action unmaximize
niri msg action move-window-between-floating-and-tiling
niri msg action spawn <program> <args...>
niri msg action spawn-sh <shell-command>
niri msg action close-window
niri msg action quit skip-confirmation=true
niri msg action screenshot
niri msg action screenshot-screen
niri msg action screenshot-window
niri msg action toggle-window-rule-opacity
niri msg action toggle-keyboard-shortcuts-inhibit
niri msg action do-screen-transition
niri msg action overview-open
niri msg action overview-close
niri msg action recent-windows-cycle
niri msg action recent-windows-cancel
niri msg action recent-windows-final
niri msg action power-off-monitors
niri msg action power-on-monitors
niri msg action set-workspace-name <name>
niri msg action unset-workspace-name
# Debug actions
niri msg action toggle-debug-tint
niri msg action debug-toggle-opaque-regions
niri msg action debug-toggle-damage
Event stream (for bars/scripts)
# Watch all events as JSON (complete state, then deltas)
niri msg --json event-stream
# Use socat to test IPC directly
socat STDIO "$NIRI_SOCKET"
# Then type a request like:
{"FocusWindow":null}
Tips
- Run
niri msg actionwith no arguments to list ALL available actions - Use
--jsonfor machine-readable output (stable API) - Output names change dynamically; use
niri msg outputsto find current names - After upgrading niri, restart it before running
niri msgto avoid version mismatch errors - The event stream gives you full state upfront, then incremental updates — no desync
Finding window app-id and title
niri msg pick-window
# Click any window — prints its app-id and title
# Example output:
# app-id: "firefox"
# title: "Mozilla Firefox"
# Or list all windows:
niri msg windows
Debugging
- Config errors:
niri validateparses the config and prints errors - Restart niri: If config reload fails, niri keeps running with old config and shows a notification
- IPC errors after upgrade:
Restart niri — old
niri msgcan't talk to new niri - Journal logs:
journalctl -ef /usr/bin/nirifor runtime errors and custom shader warnings - Find XKB key names:
Use
wev— press any key and it prints the XKB symbol name