niri
Scrollable-tiling Wayland compositor config — input, layout, window rules, per-display profiles, keybindings, and launcher scripts.
niri is a scrollable-tiling Wayland compositor. Here it runs with the Noctalia shell (bar, panels, OSD, notifications), vicinae as the launcher, and niri-dynamic-workspaces for named workspaces.
Files
| Source (repo) | Target |
|---|---|
dot_config/niri/config.kdl |
~/.config/niri/config.kdl |
dot_config/niri/noctalia.kdl |
~/.config/niri/noctalia.kdl |
dot_config/niri/exact_config.d/*.kdl |
~/.config/niri/config.d/*.kdl |
dot_config/niri/scripts/executable_* |
~/.config/niri/scripts/* (executable) |
dot_local/bin/executable_niri-launch, executable_niri-launch-{ai,browser,terminal,webapp} |
~/.local/bin/niri-launch* |
dot_local/bin/executable_cp-appid |
~/.local/bin/cp-appid |
exact_config.d is a chezmoi exact directory. On apply, chezmoi deletes any file in ~/.config/niri/config.d/ that the repo does not manage, so stale fragments cannot linger. None of these files are templates.
- ~/.config/niri/
- config.kdl
- noctalia.kdl
- config.d/
- 10-input-and-cursor.kdl
- 20-layout-and-overview.kdl
- 30-window-rules.kdl
- 40-environment.kdl
- 50-startup.kdl
- 60-animations.kdl
- 70-binds.kdl
- 80-layer-rules.kdl
- 90-outputs.kdl
- 91-internal-display.kdl
- 92-ultrawide.kdl
- 93-apple-display.kdl
- 94-portable-hdmi.kdl
- 95-portable-usbc.kdl
- scripts/
- kill-phantom-display.fish
- ndw-route-workspace.sh
- niri-floating-sidebar.sh
- niri-focus.sh
- niri-move.sh
Installation
niriandxdg-user-dirsfrom pacman,noctalia-shellfrom the AUR. Declared underpackages.arch.desktopin.chezmoidata/packages.yamland installed byrun_onchange_before_10-packages.sh.tmplfordesktop/laptoproles. See chezmoi scripts..chezmoiscripts/run_once_after_30-user-services.sh.tmplruns once on Linux. If~/.config/systemd/user/nirinit.serviceexists, it runssystemctl --user daemon-reloadandsystemctl --user enable nirinit.service. The script’s comment says other personal units (brain-lens, omp-loop, rclone, hermes, voxtype) ship as files only and are enabled by hand. The repo does not currently containdot_config/systemd, so the unit must come from elsewhere for the enable step to run.- Tools the config calls but does not install:
vicinae,cliphist/wl-paste,polkit-gnome,kbuildsycoca6,voxtype,niri-dynamic-workspaces,dot-screenshot,niri-smart-close,niri-smart-move, and thecapitaine-cursors-lightcursor theme.niri-smart-close,niri-smart-move, anddot-screenshotare called by path or name but are not in this repo.
Load order
config.kdl sets two top-level options and then includes the fragments in numeric order:
hotkey-overlay {
skip-at-startup
}
screenshot-path "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png"
include "config.d/10-input-and-cursor.kdl"
// … 20 through 90 …
include "config.d/90-outputs.kdl"
After the includes, config.kdl repeats inline input, layout, gestures, cursor, overview, window-rule, animations, and environment blocks, a leftover of the iNiR (illogical-impulse) base it started from. It then sets debug { honor-xdg-activation-with-invalid-serial }, which lets Noctalia v5 activate windows from notification actions. It includes ./noctalia.kdl and ends with prefer-no-csd. niri merges repeated sections and later values win, so the inline blocks override the fragments where they differ. Those differences are listed in each section below.
Input and cursor
config.d/10-input-and-cursor.kdl:
| Setting | Value |
|---|---|
| Keyboard layout | us |
| Repeat delay / rate | 400 ms / 50 Hz |
| Touchpad | natural-scroll, tap, tap-button-map "left-right-middle", accel-speed 1.0 |
| Tablet and touch | map-to-output "eDP-1" (pen and touchscreen stay on the laptop panel) |
| Mod key | Super; Alt when nested inside another compositor |
| Cursor | capitaine-cursors-light, size 24, hide-when-typing |
The inline input block in config.kdl repeats the same keyboard and touchpad values without accel-speed or the tablet/touch mappings, so those two settings from the fragment still apply.
Layout and overview
config.d/20-layout-and-overview.kdl:
| Setting | Value |
|---|---|
center-focused-column |
never |
background-color |
transparent (the Noctalia wallpaper shows through as backdrop) |
default-column-width |
proportion 0.5 |
preset-column-widths |
0.33333, 0.5, 0.66667 |
| Focus ring | width 1.5 |
| Border | off (width 1.5 if enabled) |
| Shadow | off (softness 30, spread 5, offset y=5 if enabled) |
| Tab indicator | place-within-column, hide-when-single-tab, position left |
| Overview | zoom 0.4, workspace shadow off |
The gestures { hot-corners { } } block is empty in the fragment. The inline block in config.kdl sets hot-corners { off }, so hot corners are disabled.
Colors (Noctalia)
noctalia.kdl is included last, so its colors override the fragment’s placeholder colors (#7fc8ff ring, #ffc87f border):
| Element | Active | Inactive | Urgent |
|---|---|---|---|
| Focus ring | #a398b3 |
#1e1c21 |
#fd4663 |
| Border | #a398b3 |
#1e1c21 |
#fd4663 |
| Tab indicator | #a398b3 |
#4a4158 |
#fd4663 |
| Recent-windows highlight | #a398b3 |
— | #fd4663 |
Shadow color is #1e1c2170 and the insert hint is #a398b380. The palette follows the Noctalia shell’s color scheme, not cyberdream.
Window rules
config.d/30-window-rules.kdl:
| Match | Rule |
|---|---|
| All windows | geometry-corner-radius 10, clip-to-geometry true; background blur on, xray off |
dev.noctalia.Noctalia (Noctalia v5 settings) |
Floating, 1080 × 920 |
Folo, Spotify, zen, brave-origin, gemini-desktop, claude-desktop |
Opacity 0.9, tiled, blur, no border background, open focused |
kitty, zed, obsidian, Beeper, chromium, com.mitchellh.ghostty |
Opacity 0.9, tiled, blur, no border background, open focused |
org.gnome.Nautilus, com.gabm.satty |
Floating, focused, width proportion 0.5 |
Title Picture-in-Picture (app zen) or Picture in picture |
Floating, not focused, opacity 1.0, 24 px from the top-right corner, on eDP-1 |
zed, obsidian, sticky-notest-canvas, title Sticky Notes, vacuumtube, Fladder, kitty, kitty-herdr, kitty-tmux |
Open on the Apple Studio Display |
Global blur settings: passes 4, offset 5.0, noise 0.02, saturation 2.0. The inline window-rule in config.kdl sets geometry-corner-radius 8 after the fragment, so the effective radius is 8.
Environment
config.d/40-environment.kdl (repeated verbatim inline in config.kdl):
| Variable | Value | Purpose |
|---|---|---|
XDG_CURRENT_DESKTOP |
niri |
|
XDG_MENU_PREFIX |
plasma- |
Dolphin/KDE file associations |
QT_QPA_PLATFORM |
wayland |
|
ELECTRON_OZONE_PLATFORM_HINT |
auto |
Native Wayland for Electron |
QT_LOGGING_RULES |
quickshell.dbus.properties=false |
Silence quickshell D-Bus logs |
QT_QPA_PLATFORMTHEME |
kde |
Qt colors from kdeglobals (needs plasma-integration) |
QT_STYLE_OVERRIDE |
Darkly |
Qt widget style |
INIR_VENV, ILLOGICAL_IMPULSE_VIRTUAL_ENV |
$HOME/.local/state/quickshell/.venv |
Quickshell Python venv |
Startup
config.d/50-startup.kdl spawns at login:
| Command | Purpose |
|---|---|
systemctl --user import-environment XDG_MENU_PREFIX && kbuildsycoca6 |
Export the menu prefix to systemd, rebuild the KDE service cache |
/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1 |
Polkit agent |
noctalia |
Shell: bar, panels, notifications, OSD, wallpaper |
niri-dynamic-workspaces daemon |
Named dynamic workspaces |
vicinae server |
Launcher backend |
wl-paste --watch cliphist store |
Clipboard history |
Animations
config.d/60-animations.kdl makes every animation a critically damped spring (damping-ratio=1.0, epsilon=0.0001) with high stiffness: workspace-switch 4000, window-open 5000, window-close 6000, horizontal-view-movement 4500, window-movement 5000, window-resize 5500, config-notification-open-close 5000, screenshot-ui-open 5000.
The inline animations block in config.kdl comes later and overrides these with softer springs tuned to match quickshell’s Material motion:
| Animation | Damping ratio | Stiffness |
|---|---|---|
| workspace-switch | 0.78 | 1350 |
| window-open | 0.82 | 1125 |
| window-close | 0.88 | 2025 |
| horizontal-view-movement | 0.80 | 1238 |
| window-movement | 0.85 | 1463 |
| window-resize | 0.88 | 1575 |
| config-notification-open-close | 0.90 | 1800 |
| screenshot-ui-open | 0.85 | 1688 |
Keybindings
All bindings from config.d/70-binds.kdl. Mod is Super (Alt when niri runs nested). Super+… and Mod+… are the same modifier. The table groups bindings by the file’s comment headers.
System and launcher
| Keys | Action |
|---|---|
Mod+Tab |
Toggle overview (no repeat) |
Mod+Shift+E |
Quit niri |
Mod+Escape |
Toggle keyboard-shortcuts inhibit (cannot itself be inhibited) |
Mod+Space |
vicinae toggle — launcher |
Mod+Shift+Space |
vicinae emoji search |
Mod+Shift+Return |
vicinae window switcher |
Super+Alt+Ctrl+Return |
vicinae window switcher |
Mod+BackSpace |
Focus previous window |
Mod+P |
vicinae clipboard history |
Mod+Ctrl+R |
Toggle the Noctalia prdlk/ostt:recorder plugin |
Mod+Period |
Noctalia control center panel |
Mod+Comma |
Noctalia settings |
Mod+Shift+Q |
Noctalia session menu (hotkey-overlay title “Session Menu: noctalia session”) |
Mod+F24 |
voxtype record toggle --profile hermes — dictation |
Screenshots
| Keys | Action |
|---|---|
Mod+S |
dot-screenshot region |
Mod+Shift+S |
dot-screenshot window |
Mod+Ctrl+S |
dot-screenshot monitor-focused |
Mod+Ctrl+Shift+S |
dot-screenshot monitor-all |
Print |
niri screenshot UI |
Ctrl+Print |
Screenshot the screen |
Alt+Print |
Screenshot the window |
Applications
| Keys | Action |
|---|---|
Mod+Shift+G |
Goliath: kitty --class=kitty-goliath --title=goliath --config ~/.config/kitty/goliath.conf (mosh + tmux; see kitty) |
Super+Ctrl+A |
Claude desktop (via vicinae) |
Super+Ctrl+G |
Gemini desktop (via vicinae) |
Super+Alt+Ctrl+H |
niri-launch-ai — cycle Claude/Hermes windows |
Super+Alt+Ctrl+J |
niri-launch-terminal — cycle terminals |
Super+Alt+Ctrl+K |
niri-launch-browser — cycle Brave Origin |
Super+Alt+Ctrl+O |
niri-launch obsidian |
Super+Alt+Ctrl+Y |
niri-launch-webapp — cycle web apps |
Super+Alt+Ctrl+W |
tldraw offline (via vicinae) |
Super+Alt+Ctrl+E |
Fastmail (via vicinae) |
Super+Alt+Ctrl+B |
Beeper (via vicinae) |
Super+Alt+Ctrl+M |
Beeper (via vicinae) |
Super+Alt+Ctrl+G |
Ghostty (via vicinae) |
Super+Alt+Ctrl+L |
Linear web app (via vicinae) |
Super+Alt+Ctrl+T |
Todoist (via vicinae) |
Super+Alt+Ctrl+N |
Open the Noctalia noctalia/notes panel |
Super+Alt+Ctrl+F |
Nautilus (via vicinae) |
Super+Alt+Ctrl+Space |
Sticky Notes Canvas (via vicinae) |
Dynamic workspaces
| Keys | Action |
|---|---|
Super+Shift+Alt+Ctrl+W |
niri-dynamic-workspaces switch w |
Super+Shift+Alt+Ctrl+H |
niri-dynamic-workspaces switch h |
Super+Alt+Ctrl+X |
niri-dynamic-workspaces switch x |
Focus and movement
| Keys | Action |
|---|---|
Mod+H |
Focus column left (wraps to last) |
Mod+L |
Focus column right (wraps to first) |
Mod+J |
Focus window below, or workspace below |
Mod+K |
Focus window above, or workspace above |
Mod+Shift+H |
niri-smart-move left |
Mod+Shift+L |
niri-smart-move right |
Mod+Shift+J |
Move column to workspace below |
Mod+Shift+K |
Move column to workspace above |
Super+Ctrl+H |
Focus first column |
Super+Ctrl+L |
Focus last column |
Super+Ctrl+Shift+H |
Move column to first |
Super+Ctrl+Shift+L |
Move column to last |
Mod+Q |
niri-smart-close (no repeat) |
Monitors
| Keys | Action |
|---|---|
Mod+Left |
Focus monitor left |
Mod+Up |
Focus monitor up |
Mod+Right |
Focus monitor right |
Mod+Shift+Down |
Move column to monitor right |
Mod+Shift+Up |
Move column to monitor left |
Super+O |
Focus next monitor |
Super+Ctrl+J |
Focus next monitor |
Super+Ctrl+K |
Focus previous monitor |
Super+Shift+O |
Move column to next monitor |
Mod+Ctrl+Shift+J |
Move column to next monitor |
Mod+Ctrl+Shift+K |
Move column to previous monitor |
Layout and sizing
| Keys | Action |
|---|---|
Mod+R |
Cycle preset column widths |
Mod+Shift+R |
Cycle preset window heights |
Mod+F |
Expand column to available width |
Mod+Shift+F |
Maximize column |
Mod+Ctrl+F |
Fullscreen window |
Super+C |
Center column |
Mod+Ctrl+C |
Center visible columns |
Mod+Ctrl+O |
Toggle window floating |
Mod+W |
Toggle tabbed column display |
Alt+Shift+H |
Column width −10% |
Alt+Shift+L |
Column width +10% |
Alt+Shift+J |
Window height −10% |
Alt+Shift+K |
Window height +10% |
Hardware keys
All of these work while the screen is locked (allow-when-locked=true) and go through Noctalia IPC, so the Noctalia OSD shows feedback. The commented v4: lines in the file are the old qs -c noctalia-shell ipc call … forms.
| Keys | Action |
|---|---|
XF86AudioRaiseVolume |
noctalia msg volume-up |
XF86AudioLowerVolume |
noctalia msg volume-down |
XF86AudioMute |
noctalia msg volume-mute |
XF86AudioMicMute |
noctalia msg mic-mute |
XF86AudioNext |
noctalia msg media next |
XF86AudioPrev |
noctalia msg media previous |
XF86AudioPlay / XF86AudioPause |
noctalia msg media toggle |
XF86MonBrightnessUp |
noctalia msg brightness-up |
XF86MonBrightnessDown |
noctalia msg brightness-down |
Layer rules
config.d/80-layer-rules.kdl:
- Noctalia surfaces matching
^noctalia-(bar-…|notification|dock|panel|attached-panel|osd)$getbackground-effect { xray false }, so their blur samples what is actually behind them. Noctalia publishes blur regions itself whenext-background-effectsis available. ^noctalia-wallpaperis placed within the backdrop (place-within-backdrop true). The wallpaper stays still while workspaces move and shows in the overview.layout { background-color "transparent" }and overviewworkspace-shadow { off }are restated here so the backdrop is always visible.
Outputs and display profiles
config.d/90-outputs.kdl turns DP-5 off and includes the per-display profiles. An older Apple Display block and a DP-4 off block remain as comments.
include "91-internal-display.kdl"
// include "92-ultrawide.kdl"
include "93-apple-display.kdl"
include "94-portable-hdmi.kdl"
// include "95-portable-usbc.kdl"
The Apple Studio Display also exposes a phantom connector (DP-4/5/6, varies per boot) whose only mode is 640×480. A commented spawn-at-startup for scripts/kill-phantom-display.fish handles it by mode signature, since connector name and EDID both shift.
Tianma Microelectronics Ltd. TL134ADXP03 Unknown, the laptop panel. Active.
| Setting | Value |
|---|---|
| Position / scale | 0,0 / 1.5 |
| VRR | on-demand=true |
| Gaps | 8 |
| Default column width | 1.0 |
| Preset widths | 0.5, 1.0 |
| Centering | always-center-single-column, center-focused-column "never" |
Named workspaces 1, 2, 3 open on this panel. Three window rules open these apps maximized and focused on workspace 1, without border backgrounds (all three target "1" as written):
chrome-youtube.com__-Default,chromiumorg.gnome.Calendar,discord,slack,com-fastmail-fastmail,gemini-desktop,Beeper,com.anthropic.Claude,Hermes,todoistWebApp-CloudflarePersonal7594,WebApp-CloudflareWalletbase8881
Samsung Electric Company S34CG50 HNTY501103. Included in the repo but commented out.
| Setting | Value |
|---|---|
| Mode | 3440x1440@100.00 |
| Position / scale | 0,0 / 1 |
| VRR | on-demand=true |
| Gaps | 16 |
| Default column width | 0.3333 |
| Preset widths | 0.3333, 0.5, 0.6667 |
| Centering | always-center-single-column, center-focused-column "never" |
Apple Computer Inc StudioDisplay 0x984262A7. Active.
| Setting | Value |
|---|---|
| Scale | 1.75 |
| Gaps | 8 |
| Default column width | 0.5 |
| Preset widths | 0.25, 0.3333, 0.5, 0.6667 |
| Centering | always-center-single-column, center-focused-column "never" |
PNP(AOP) 16PM1QB 14490001D3X00. Active.
| Setting | Value |
|---|---|
| Transform | 90 (portrait) |
| Gaps | 2 |
| Default column width | 1.0 |
| Preset widths | 0.5, 1.0 |
| Centering | always-center-single-column, center-focused-column "never" |
ASUSTek COMPUTER INC ASUS MB16AC M8LMTF195749. Included in the repo but commented out.
| Setting | Value |
|---|---|
| Position | 0,0 |
| Gaps | 2 |
| Default column width | 1.0 |
| Preset widths | 0.5, 1.0 |
| Centering | always-center-single-column, center-focused-column "never" |
To switch a profile, uncomment or comment its include line in 90-outputs.kdl. niri reloads config on save.
Scripts
Deployed to ~/.config/niri/scripts/. 70-binds.kdl binds none of them, so they run by hand or from other tools.
| Script | Usage | Behavior |
|---|---|---|
kill-phantom-display.fish |
no args | Polls niri msg -j outputs up to 10 times, 1 s apart. Any output whose only mode is 640×480 gets niri msg output <name> off. Does not handle mid-session hotplug. |
ndw-route-workspace.sh |
$NDW_WORKSPACE_KEY or $1 |
niri-dynamic-workspaces on_create hook. Keys y g m x z p l a move the workspace to HDMI-A-1. Keys b f s w c d o move it to the Apple Studio Display (resolved by make/model through niri msg --json outputs), or to HDMI-A-1 if that display is absent. Other keys exit silently. |
niri-floating-sidebar.sh |
toggle | hide | flip | reorder | help |
Per-workspace floating sidebar on the right edge. Windows are 550 px wide and stack into 4 slots (more as needed) with 15 px gaps and 30/50/20 px top/bottom/side margins. toggle floats the focused window into the sidebar or restores it to tiling at its original size. hide slides the sidebar off-screen, leaving a 70 px sliver. flip switches bottom-up and top-down stacking. State is kept in /tmp/niri.toggle.<workspace>.*. |
niri-focus.sh |
up | down |
Cascading focus: next window in the column, then the next occupied workspace (skipping the trailing empty one), then the adjacent monitor. Wraps to the far-end monitor at the edge. |
niri-move.sh |
up | down |
Cascading move: reorder within the column, then move to the next workspace (including the empty trailing one), then the adjacent monitor. Wraps at the edge. |
Launchers
niri-launch
~/.local/bin/niri-launch <app> focuses an app’s window, cycles through its windows on repeated presses (sorted by window id), or launches it when none is open.
| Argument | Windows matched (app-id) | Launched when none |
|---|---|---|
webapp |
^(webapp-|chrome-) (Firefox and Chromium/Brave site-specific apps) |
Re-runs as webapp-youtube |
webapp-NAME |
WebApp-Name (case-insensitive) |
gtk-launch on ~/.local/share/applications/WebApp-Name*.desktop |
kitty |
any app-id starting with kitty |
kitty |
kitty-CLASS |
exactly that class | kitty --class kitty-CLASS (optional command from the empty KITTY_CMDS map) |
ghostty |
ghostty |
nothing, prints a message |
browser |
^(zen|brave|chromium|firefox) |
zen |
terminal |
kitty|alacritty|ghostty |
kitty |
ai |
claude|hermes |
hermes desktop |
| anything else | exact app-id | the argument as a command |
Wrappers
Each wrapper resolves its own symlink and execs niri-launch from the same directory:
| Script | Runs | Bound to |
|---|---|---|
niri-launch-ai |
niri-launch ai |
Super+Alt+Ctrl+H |
niri-launch-terminal |
niri-launch terminal |
Super+Alt+Ctrl+J |
niri-launch-browser |
niri-launch brave-origin (exact app-id, not the browser mode) |
Super+Alt+Ctrl+K |
niri-launch-webapp |
niri-launch webapp |
Super+Alt+Ctrl+Y |
cp-appid
cp-appid lists every .desktop file (via fd) in /usr/share/applications, /usr/local/share/applications, ~/.local/share/applications, the system and user Flatpak export dirs, and /var/lib/snapd/desktop/applications. It shows each entry’s Name= and ID in fzf, then copies the chosen ID to the clipboard with wl-copy. Use it to find app-ids for window rules and niri-launch.