niri Documentation

A scrollable-tiling Wayland compositor.

26,853 stars Rust Rust 1 file Β· ~16,996 tokens #rust#smithay#tiling-window-manager#wayland#wayland-compositor
RAW Doc

File: README.md

About

Windows are arranged in columns on an infinite strip going to the right.
Opening a new window never causes existing windows to resize.

Every monitor has its own separate window strip.
Windows can never "overflow" onto an adjacent monitor.

Workspaces are dynamic and arranged vertically.
Every monitor has an independent set of workspaces, and there's always one empty workspace present all the way down.

The workspace arrangement is preserved across disconnecting and connecting monitors where it makes sense.
When a monitor disconnects, its workspaces will move to another monitor, but upon reconnection they will move back to the original monitor.

Features

Video Demo

https://github.com/niri-wm/niri/assets/1794388/bce834b0-f205-434e-a027-b373495f9729

Also check out these videos that showcase a lot of the niri functionality:

Status

Niri is stable for day-to-day use and does most things expected of a Wayland compositor.
Many people are daily-driving niri, and are happy to help in our Matrix channel.

Give it a try!
Follow the instructions on the Getting Started page.
Grab a desktop shell like DankMaterialShell or Noctalia (or build a more traditional setup): niri by itself is not a complete desktop environment.
Also check out awesome-niri, a list of niri-related links and projects.

Here are some points you may have questions about:

  • Multi-monitor: yes, a core part of the design from the very start. Mixed DPI works.
  • Fractional scaling: yes, plus all niri UI stays pixel-perfect.
  • NVIDIA: seems to work fine.
  • Floating windows: yes, starting from niri 25.01.
  • Input devices: niri supports tablets, touchpads, and touchscreens.
    You can map the tablet to a specific monitor, or use OpenTabletDriver.
    We have touchpad gestures, but no touchscreen gestures yet.
  • Wlr protocols: yes, we have most of the important ones like layer-shell, gamma-control, screencopy.
    You can check on wayland.app at the bottom of each protocol's page.
  • Performance: while I run niri on beefy machines, I try to stay conscious of performance.
    I've seen someone use it fine on an EeeΒ PCΒ 900 fromΒ 2008, of all things.
  • Xwayland: integrated via xwayland-satellite starting from niri 25.08.

Media

niri: Making a Wayland compositor in Rust Β· December 2024

My talk from the 2024 Moscow RustCon about niri, and how I do randomized property testing and profiling, and measure input latency.
The talk is in Russian, but I prepared full English subtitles that you can find in YouTube's subtitle language selector.

An interview with Ivan, the developer behind Niri Β· June 2025

An interview by a German tech podcast Das Triumvirat (in English).
We talk about niri development and history, and my experience building and maintaining niri.

A tour of the niri scrolling-tiling Wayland compositor Β· July 2025

An LWN article with a nice overview and introduction to niri.

Contributing

If you'd like to help with niri, there are plenty of both coding- and non-coding-related ways to do so.
See CONTRIBUTING.md for an overview.

Inspiration

Niri is heavily inspired by PaperWM which implements scrollable tiling on top of GNOME Shell.

One of the reasons that prompted me to try writing my own compositor is being able to properly separate the monitors.
Being a GNOME Shell extension, PaperWM has to work against Shell's global window coordinate space to prevent windows from overflowing.

Tile Scrollably Elsewhere

Here are some other projects which implement a similar workflow:

Contact

Our main communication channel is a Matrix chat, feel free to join and ask a question: https://matrix.to/#/#niri:matrix.org

We also have a community Discord server: https://discord.gg/vT8Sfjy7sx


File: docs/wiki/_Sidebar.md

Usage

Configuration

Development


File: docs/wiki/Accessibility.md

Screen readers

Since: 25.08

Niri has basic support for screen readers (specifically, Orca) when running as a full desktop session, i.e. you need to start niri through a display manager or through niri-session.
To avoid conflicts with an already running compositor, niri won't expose accessibility interfaces when started as a nested window, or as a plain /usr/bin/niri on a TTY.

We implement the org.freedesktop.a11y.KeyboardMonitor D-Bus interface for Orca to listen and grab keyboard keys, and we expose the main niri UI elements via AccessKit.
Specifically, niri will announce:

  • workspace switching, for example it'll say "Workspace 2" when you switch to the second workspace;
  • the exit confirmation dialog (appears on SuperShiftE by default);
  • Since: 25.11 niri has an AltTab window switcher where it will announce the selected window title;
  • entering the screenshot UI and the overview (niri will say when these are focused, nothing else for now);
  • whenever a config parse error occurs;
  • the important hotkeys list (for now, as one big announcement without tab navigation; appears on SuperShift/ by default).

Here's a demo video, watch with sound on.

Make sure Xwayland works, then run orca.
The default config binds SuperAltS to toggle Orca, which is the standard key binding.

Note that there are some limitations:

  • We don't have a bind to move focus to layer-shell panels. This is not hard to add, but it would be good to have some consensus or prior art with LXQt/Xfce on how exactly this should work.
  • You need to have a screen connected and enabled. Without a screen, niri won't give focus any window. This makes sense for sighted users, and I'm not entirely sure what makes the most sense for accessibility purposes (maybe, it'd be better solved with virtual monitors).
  • You need working EGL (hardware acceleration).
  • We don't have screen curtain functionality yet.

If you're shipping niri and would like to make it work better for screen readers out of the box, consider the following changes to the default niri config:

  • Change the default terminal from Alacritty to one that supports screen readers. For example, GNOME Console or GNOME Terminal should work well.
  • Change the default application launcher and screen locker to ones that support screen readers. For example, xfce4-appfinder is an accessible launcher. Suggestions welcome! Likely, something GTK-based will work fine.
  • Add some spawn-at-startup command that plays a sound which will indicate to users that niri has finished loading.
  • Add spawn-at-startup "orca" to run Orca automatically at niri startup.

Desktop zoom

There's no built-in zoom yet, but you can use third-party utilities like wooz.


File: docs/wiki/Application-Issues.md

Electron applications

Electron-based applications can run directly on Wayland, but it's not the default.

For Electron β‰₯ 39, you can use the command-line flag if the app does not default to Wayland:

text
--ozone-platform=wayland

For Electron < 39, you can set an environment variable:

kdl
environment {
    ELECTRON_OZONE_PLATFORM_HINT "auto"
}

For Electron ≀ 28, you need to pass command-line flags to the target application:

text
--enable-features=UseOzonePlatform --ozone-platform-hint=auto

If the application has a desktop entry, you can put the command-line arguments into the Exec section.

VSCode

If you're having issues with some VSCode hotkeys, try starting Xwayland and setting the DISPLAY=:0 environment variable for VSCode.
That is, still running VSCode with the Wayland backend, but with DISPLAY set to a running Xwayland instance.
Apparently, VSCode currently unconditionally queries the X server for a keymap.

JetBrains IDEs

JetBrains IDEs can run directly on Wayland, but it's not the default.

For JetBrainsRuntime > 17, you can set the flag -Dawt.toolkit.name=WLToolkit inside of help -> edit custom vm options -> add.

If the settings window fails to load under Wayland, and the UI becomes unresponsive afterwards, also set the flag -Dsun.awt.wl.WindowDecorationStyle=builtin in the custom vm options. This gives the settings window a titlebar, but it at least makes the IDE functional.

WezTerm

[!NOTE]
Both of these issues seem to be fixed in the nightly build of WezTerm.

There's a bug in WezTerm that it waits for a zero-sized Wayland configure event, so its window never shows up in niri. To work around it, put this window rule in the niri config (included in the default config):

kdl
window-rule {
    match app-id=r#"^org\.wezfurlong\.wezterm$"#
    default-column-width {}
}

This empty default column width lets WezTerm pick its own initial width which makes it show up properly.

There's another bug in WezTerm that causes it to choose a wrong size when it's in a tiled state, and prevent resizing it.
Niri puts windows in the tiled state with prefer-no-csd.
So if you hit this problem, comment out prefer-no-csd in the niri config and restart WezTerm.

Ghidra

Some Java apps like Ghidra can show up blank under xwayland-satellite.
To fix this, run them with the _JAVA_AWT_WM_NONREPARENTING=1 environment variable.

Zen Browser

For some reason, DMABUF screencasts are disabled in the Zen Browser, so screencasting doesn't work out of the box on niri.
To fix it, open about:config and set widget.dmabuf.force-enabled to true.

GTK 4 dead keys / Compose

GTK 4.20 stopped handling dead keys and Compose on its own on Wayland.
To make them work, either run an IME like IBus or Fcitx5, or set the GTK_IM_MODULE=simple environment variable.

kdl
environment {
    GTK_IM_MODULE "simple"
}

Note that the niri environment config does not propagate to apps and shells started by systemd, for example to DankMaterialShell and its application launcher.
You can set the variable in your login shell config (i.e. ~/.bash_profile) instead, though keep in mind that then it will be set for all compositors, not just niri.

Fullscreen games

Some video games, both Linux-native and on Wine, have various issues when using non-stacking desktop environments.
Most of these can be avoided with Valve's gamescope, for example:

sh
gamescope -f -w 1920 -h 1080 -W 1920 -H 1080 --force-grab-cursor --backend sdl -- <game>

This command will run in 1080p fullscreenβ€”make sure to replace the width and height values to match your desired resolution.
--force-grab-cursor forces gamescope to use relative mouse movement which prevents the cursor from escaping the game's window on multi-monitor setups.
Note that --backend sdl is currently also required as gamescope's default Wayland backend doesn't lock the cursor properly (possibly related to https://github.com/ValveSoftware/gamescope/issues/1711).

Steam users should use gamescope through a game's launch options by replacing the game executable with %command%.
Other game launchers such as Lutris have their own ways of setting gamescope options.

Running X11-based games with this method doesn't require Xwayland as gamescope creates its own Xwayland server.
You can run Wayland-native games as well by passing --expose-wayland to gamescope, therefore eliminating X11 from the equation.

Steam

On some systems, Steam will show a fully black window.
To fix this, navigate to Settings -> Interface (via Steam's tray icon, or by blindly finding the Steam menu at the top left of the window), then disable GPU accelerated rendering in web views.
Restart Steam and it should now work fine.

If you do not want to disable GPU accelerated rendering you can instead try to pass the launch argument -system-composer instead.

Steam notifications don't run through the standard notification daemon and show up as floating windows in the center of the screen.
You can move them to a more convenient location by adding a window rule in your niri config:

kdl
window-rule {
    match app-id="steam" title=r#"^notificationtoasts_\d+_desktop$"#
    default-floating-position x=10 y=10 relative-to="bottom-right"
}

Waybar and other GTK 3 components

If you have rounded corners on your Waybar and they show up with black pixels in the corners, then set your Waybar opacity to 0.99, which should fix it.

GTK 3 seems to have a bug where it reports a surface as fully opaque even if it has rounded corners.
This leads to niri filling the transparent pixels inside the corners with black.

Setting the surface opacity to something below 1 fixes the problem because then GTK no longer reports the surface as opaque.


File: docs/wiki/Configuration:-Animations.md

Overview

Niri has several animations which you can configure in the same way.
Additionally, you can disable or slow down all animations at once.

Here's a quick glance at the available animations with their default values.

kdl
animations {
    // Uncomment to turn off all animations.
    // You can also put "off" into each individual animation to disable it.
    // off

    // Slow down all animations by this factor. Values below 1 speed them up instead.
    // slowdown 3.0

    // Individual 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
    }

    config-notification-open-close {
        spring damping-ratio=0.6 stiffness=1000 epsilon=0.001
    }

    exit-confirmation-open-close {
        spring damping-ratio=0.6 stiffness=500 epsilon=0.01
    }

    screenshot-ui-open {
        duration-ms 200
        curve "ease-out-quad"
    }

    overview-open-close {
        spring damping-ratio=1.0 stiffness=800 epsilon=0.0001
    }

    recent-windows-close {
        spring damping-ratio=1.0 stiffness=800 epsilon=0.001
    }
}

Animation Types

There are two animation types: easing and spring.
Each animation can be either an easing or a spring.

Easing

This is a relatively common animation type that changes the value over a set duration using an interpolation curve.

To use this animation, set the following parameters:

  • duration-ms: duration of the animation in milliseconds.
  • curve: the easing curve to use.
kdl
animations {
    window-open {
        duration-ms 150
        curve "ease-out-expo"
    }
}

Currently, niri only supports five curves.
You can get a feel for them on pages like easings.net.

  • ease-out-quad Since: 0.1.5
  • ease-out-cubic
  • ease-out-expo
  • linear Since: 0.1.6
  • cubic-bezier Since: 25.08
    A custom cubic BΓ©zier curve. You need to set 4 numbers defining the control points of the curve, for example:
    kdl
    animations {
        window-open {
            // Same as CSS cubic-bezier(0.05, 0.7, 0.1, 1)
            curve "cubic-bezier" 0.05 0.7 0.1 1
        }
    }
    You can tweak the cubic-bezier parameters on pages like easings.co.

Spring

Spring animations use a model of a physical spring to animate the value.
They notably feel better with touchpad gestures, because they take into account the velocity of your fingers as you release the swipe.
Springs can also oscillate / bounce at the end with the right parameters if you like that sort of thing, but they don't have to (and by default they mostly don't).

Due to springs using a physical model, the animation parameters are less obvious and generally should be tuned with trial and error.
Notably, you cannot directly set the duration.
You can use the Elastic app to help visualize how the spring parameters change the animation.

A spring animation is configured like this, with three mandatory parameters:

kdl
animations {
    workspace-switch {
        spring damping-ratio=1.0 stiffness=1000 epsilon=0.0001
    }
}

The damping-ratio goes from 0.1 to 10.0 and has the following properties:

  • below 1.0: underdamped spring, will oscillate in the end.
  • above 1.0: overdamped spring, won't oscillate.
  • 1.0: critically damped spring, comes to rest in minimum possible time without oscillations.

However, even with damping ratio = 1.0, the spring animation may oscillate if "launched" with enough velocity from a touchpad swipe.

[!WARNING]
Overdamped springs currently have some numerical stability issues and may cause graphical glitches.
Therefore, setting damping-ratio above 1.0 is not recommended.

Lower stiffness will result in a slower animation more prone to oscillation.

Set epsilon to a lower value if the animation "jumps" at the end.

[!TIP]
The spring mass (which you can see in Elastic) is hardcoded to 1.0 and cannot be changed.
Instead, change stiffness proportionally.
E.g. increasing mass by 2Γ— is the same as decreasing stiffness by 2Γ—.

Animations

Now let's go into more detail on the animations that you can configure.

`workspace-switch`

Animation when switching workspaces up and down, including after the vertical touchpad gesture (a spring is recommended).

kdl
animations {
    workspace-switch {
        spring damping-ratio=1.0 stiffness=1000 epsilon=0.0001
    }
}

`window-open`

Window opening animation.

This one uses an easing type by default.

kdl
animations {
    window-open {
        duration-ms 150
        curve "ease-out-expo"
    }
}

`custom-shader`

Since: 0.1.6

You can write a custom shader for drawing the window during an open animation.

See this example shader for a full documentation with several animations to experiment with.

If a custom shader fails to compile, niri will print a warning and fall back to the default, or previous successfully compiled shader.
When running niri as a systemd service, you can see the warnings in the journal: journalctl -ef /usr/bin/niri

[!WARNING]

Custom shaders do not have a backwards compatibility guarantee.
I may need to change their interface as I'm developing new features.

Example: open will fill the current geometry with a solid gradient that gradually fades in.

kdl
animations {
    window-open {
        duration-ms 250
        curve "linear"

        custom-shader r"
            vec4 open_color(vec3 coords_geo, vec3 size_geo) {
                vec4 color = vec4(0.0);

                if (0.0 <= coords_geo.x && coords_geo.x <= 1.0
                        && 0.0 <= coords_geo.y && coords_geo.y <= 1.0)
                {
                    vec4 from = vec4(1.0, 0.0, 0.0, 1.0);
                    vec4 to = vec4(0.0, 1.0, 0.0, 1.0);
                    color = mix(from, to, coords_geo.y);
                }

                return color * niri_clamped_progress;
            }
        "
    }
}

`window-close`

Since: 0.1.5

Window closing animation.

This one uses an easing type by default.

kdl
animations {
    window-close {
        duration-ms 150
        curve "ease-out-quad"
    }
}

`custom-shader`

Since: 0.1.6

You can write a custom shader for drawing the window during a close animation.

See this example shader for a full documentation with several animations to experiment with.

If a custom shader fails to compile, niri will print a warning and fall back to the default, or previous successfully compiled shader.
When running niri as a systemd service, you can see the warnings in the journal: journalctl -ef /usr/bin/niri

[!WARNING]

Custom shaders do not have a backwards compatibility guarantee.
I may need to change their interface as I'm developing new features.

Example: close will fill the current geometry with a solid gradient that gradually fades away.

kdl
animations {
    window-close {
        custom-shader r"
            vec4 close_color(vec3 coords_geo, vec3 size_geo) {
                vec4 color = vec4(0.0);

                if (0.0 <= coords_geo.x && coords_geo.x <= 1.0
                        && 0.0 <= coords_geo.y && coords_geo.y <= 1.0)
                {
                    vec4 from = vec4(1.0, 0.0, 0.0, 1.0);
                    vec4 to = vec4(0.0, 1.0, 0.0, 1.0);
                    color = mix(from, to, coords_geo.y);
                }

                return color * (1.0 - niri_clamped_progress);
            }
        "
    }
}

`horizontal-view-movement`

All horizontal camera view movement animations, such as:

  • When a window off-screen is focused and the camera scrolls to it.
  • When a new window appears off-screen and the camera scrolls to it.
  • After a horizontal touchpad gesture (a spring is recommended).
kdl
animations {
    horizontal-view-movement {
        spring damping-ratio=1.0 stiffness=800 epsilon=0.0001
    }
}

`window-movement`

Since: 0.1.5

Movement of individual windows within a workspace.

Includes:

  • Moving window columns with move-column-left and move-column-right.
  • Moving windows inside a column with move-window-up and move-window-down.
  • Moving windows out of the way upon window opening and closing.
  • Window movement between columns when consuming/expelling.

This animation does not include the camera view movement, such as scrolling the workspace left and right.

kdl
animations {
    window-movement {
        spring damping-ratio=1.0 stiffness=800 epsilon=0.0001
    }
}

`window-resize`

Since: 0.1.5

Window resize animation.

Only manual window resizes are animated, i.e. when you resize the window with switch-preset-column-width or maximize-column.
Also, very small resizes (up to 10 pixels) are not animated.

kdl
animations {
    window-resize {
        spring damping-ratio=1.0 stiffness=800 epsilon=0.0001
    }
}

`custom-shader`

Since: 0.1.6

You can write a custom shader for drawing the window during a resize animation.

See this example shader for a full documentation with several animations to experiment with.

If a custom shader fails to compile, niri will print a warning and fall back to the default, or previous successfully compiled shader.
When running niri as a systemd service, you can see the warnings in the journal: journalctl -ef /usr/bin/niri

[!WARNING]

Custom shaders do not have a backwards compatibility guarantee.
I may need to change their interface as I'm developing new features.

Example: resize will show the next (after resize) window texture right away, stretched to the current geometry.

kdl
animations {
    window-resize {
        custom-shader r"
            vec4 resize_color(vec3 coords_curr_geo, vec3 size_curr_geo) {
                vec3 coords_tex_next = niri_geo_to_tex_next * coords_curr_geo;
                vec4 color = texture2D(niri_tex_next, coords_tex_next.st);
                return color;
            }
        "
    }
}

`config-notification-open-close`

The open/close animation of the config parse error and new default config notifications.

This one uses an underdamped spring by default (damping-ratio=0.6) which causes a slight oscillation in the end.

kdl
animations {
    config-notification-open-close {
        spring damping-ratio=0.6 stiffness=1000 epsilon=0.001
    }
}

`exit-confirmation-open-close`

Since: 25.08

The open/close animation of the exit confirmation dialog.

This one uses an underdamped spring by default (damping-ratio=0.6) which causes a slight oscillation in the end.

kdl
animations {
    exit-confirmation-open-close {
        spring damping-ratio=0.6 stiffness=500 epsilon=0.01
    }
}

`screenshot-ui-open`

Since: 0.1.8

The open (fade-in) animation of the screenshot UI.

kdl
animations {
    screenshot-ui-open {
        duration-ms 200
        curve "ease-out-quad"
    }
}

`overview-open-close`

Since: 25.05

The open/close zoom animation of the Overview.

kdl
animations {
    overview-open-close {
        spring damping-ratio=1.0 stiffness=800 epsilon=0.0001
    }
}

`recent-windows-close`

Since: 25.11

The close fade-out animation of the recent windows switcher.

kdl
animations {
    recent-windows-close {
        spring damping-ratio=1.0 stiffness=800 epsilon=0.001
    }
}

Synchronized Animations

Since: 0.1.5

Sometimes, when two animations are meant to play together synchronized, niri will drive them both with the same configuration.

For example, if a window resize causes the view to move, then that view movement animation will also use the window-resize configuration (rather than the horizontal-view-movement configuration).
This is especially important for animated resizes to look good when using center-focused-column "always".

As another example, resizing a window in a column vertically causes other windows to move up or down into their new position.
This movement will use the window-resize configuration, rather than the window-movement configuration, to keep the animations synchronized.

A few actions are still missing this synchronization logic, since in some cases it is difficult to implement properly.
Therefore, for the best results, consider using the same parameters for related animations (they are all the same by default):

  • horizontal-view-movement
  • window-movement
  • window-resize

File: docs/wiki/Configuration:-Debug-Options.md

Overview

Niri has several options that are only useful for debugging, or are experimental and have known issues.
They are not meant for normal use.

[!CAUTION]
These options are not covered by the config breaking change policy.
They can change or stop working at any point with little notice.

Here are all the options at a glance:

kdl
debug {
    preview-render "screencast"
    // preview-render "screen-capture"
    enable-overlay-planes
    disable-cursor-plane
    disable-direct-scanout
    restrict-primary-scanout-to-matching-format
    force-disable-connectors-on-resume
    render-drm-device "/dev/dri/renderD129"
    ignore-drm-device "/dev/dri/renderD128"
    ignore-drm-device "/dev/dri/renderD130"
    force-pipewire-invalid-modifier
    dbus-interfaces-in-non-session-instances
    wait-for-frame-completion-before-queueing
    emulate-zero-presentation-time
    disable-resize-throttling
    disable-transactions
    keep-laptop-panel-on-when-lid-is-closed
    disable-monitor-names
    strict-new-window-focus-policy
    honor-xdg-activation-with-invalid-serial
    skip-cursor-only-updates-during-vrr
    deactivate-unfocused-windows
    disable-10bit-output
}

binds {
    Mod+Shift+Ctrl+T { toggle-debug-tint; }
    Mod+Shift+Ctrl+O { debug-toggle-opaque-regions; }
    Mod+Shift+Ctrl+D { debug-toggle-damage; }
}

`preview-render`

Make niri render the monitors the same way as for a screencast or a screen capture.

Useful for previewing the block-out-from window rule.

kdl
debug {
    preview-render "screencast"
    // preview-render "screen-capture"
}

`enable-overlay-planes`

Enable direct scanout into overlay planes.
May cause frame drops during some animations on some hardware (which is why it is not the default).

Direct scanout into the primary plane is always enabled.

kdl
debug {
    enable-overlay-planes
}

`disable-cursor-plane`

Disable the use of the cursor plane.
The cursor will be rendered together with the rest of the frame.

Useful to work around driver bugs on specific hardware.

kdl
debug {
    disable-cursor-plane
}

`disable-direct-scanout`

Disable direct scanout to both the primary plane and the overlay planes.

kdl
debug {
    disable-direct-scanout
}

`restrict-primary-scanout-to-matching-format`

Restricts direct scanout to the primary plane to when the window buffer exactly matches the composition swapchain format.

This flag may prevent unexpected bandwidth changes when going between composition and scanout.
The plan is to make it default in the future, when we implement a way to tell the clients the composition swapchain format.
As is, it may prevent some clients (mpv on my machine) from scanning out to the primary plane.

kdl
debug {
    restrict-primary-scanout-to-matching-format
}

`force-disable-connectors-on-resume`

Since: 26.04

Force-disables all outputs upon resuming niri (TTY switch or waking up from suspend).
This causes a modeset/screen blank on all outputs.

If niri rendering is corrupted, or monitors don't light up after a TTY switch, you can try this flag.

kdl
debug {
    force-disable-connectors-on-resume
}

`render-drm-device`

Override the DRM device that niri will use for all rendering.

You can set this to make niri use a different primary GPU than the default one.

kdl
debug {
    render-drm-device "/dev/dri/renderD129"
}

`ignore-drm-device`

Since: 25.11

List DRM devices that niri will ignore.
Useful for GPU passthrough when you don't want niri to open a certain device.

kdl
debug {
    ignore-drm-device "/dev/dri/renderD128"
    ignore-drm-device "/dev/dri/renderD130"
}

`force-pipewire-invalid-modifier`

Since: 25.01

Forces PipeWire screencasting to use the invalid modifier, even when DRM offers more modifiers.

Useful for testing the invalid modifier code path that is hit by drivers that don't support modifiers.

kdl
debug {
    force-pipewire-invalid-modifier
}

`dbus-interfaces-in-non-session-instances`

Make niri create its D-Bus interfaces even if it's not running as a --session.

Useful for testing screencasting changes without having to relogin.

The main niri instance will not currently take back the interfaces when you close the test instance, so you will need to relogin in the end to make screencasting work again.

kdl
debug {
    dbus-interfaces-in-non-session-instances
}

`wait-for-frame-completion-before-queueing`

Wait until every frame is done rendering before handing it over to DRM.

Useful for diagnosing certain synchronization and performance problems.

kdl
debug {
    wait-for-frame-completion-before-queueing
}

`emulate-zero-presentation-time`

Emulate zero (unknown) presentation time returned from DRM.

This is a thing on NVIDIA proprietary drivers, so this flag can be used to test that niri doesn't break too hard on those systems.

kdl
debug {
    emulate-zero-presentation-time
}

`disable-resize-throttling`

Since: 0.1.9

Disable throttling resize events sent to windows.

By default, when resizing quickly (e.g. interactively), a window will only receive the next size once it has made a commit for the previously requested size.
This is required for resize transactions to work properly, and it also helps certain clients which don't batch incoming resizes from the compositor.

Disabling resize throttling will send resizes to windows as fast as possible, which is potentially very fast (for example, on a 1000 Hz mouse).

kdl
debug {
    disable-resize-throttling
}

`disable-transactions`

Since: 0.1.9

Disable transactions (resize and close).

By default, windows which must resize together, do resize together.
For example, all windows in a column must resize at the same time to maintain the combined column height equal to the screen height, and to maintain the same window width.

Transactions make niri wait until all windows finish resizing before showing them all on screen in one, synchronized frame.
For them to work properly, resize throttling shouldn't be disabled (with the previous debug flag).

kdl
debug {
    disable-transactions
}

`keep-laptop-panel-on-when-lid-is-closed`

Since: 0.1.10

By default, niri will disable the internal laptop monitor when the laptop lid is closed.
This flag turns off this behavior and will leave the internal laptop monitor on.

kdl
debug {
    keep-laptop-panel-on-when-lid-is-closed
}

`disable-monitor-names`

Since: 0.1.10

Disables the make/model/serial monitor names, as if niri fails to read them from the EDID.

Use this flag to work around a crash present in 0.1.9 and 0.1.10 when connecting two monitors with matching make/model/serial.

kdl
debug {
    disable-monitor-names
}

`strict-new-window-focus-policy`

Since: 25.01

Disables heuristic automatic focusing for new windows.
Only windows that activate themselves with a valid xdg-activation token will be focused.

kdl
debug {
    strict-new-window-focus-policy
}

`honor-xdg-activation-with-invalid-serial`

Since: 25.05

Widely-used clients such as Discord and Telegram make fresh xdg-activation tokens upon clicking on their tray icon or on their notification.
Most of the time, these fresh tokens will have invalid serials, because the app needs to be focused to get a valid serial, and if the user clicks on a tray icon or a notification, it is usually because the app isn't focused, and the user wants to focus it.

By default, niri ignores xdg-activation tokens with invalid serials, to prevent windows from randomly stealing focus.
This debug flag makes niri honor such tokens, making the aforementioned widely-used apps get focus when clicking on their tray icon or notification.

Use the on-xdg-activate window rule to control what niri does for individual windows when it accepts an xdg-activation request.

Amusingly, clicking on a notification sends the app a perfectly valid activation token from the notification daemon, but these apps seem to simply ignore it.
Maybe in the future these apps/toolkits (Electron, Qt) are fixed, making this debug flag unnecessary.

kdl
debug {
    honor-xdg-activation-with-invalid-serial
}

`skip-cursor-only-updates-during-vrr`

Since: 25.08

Skips redrawing the screen from cursor input while variable refresh rate is active.

Useful for games where the cursor isn't drawn internally to prevent erratic VRR shifts in response to cursor movement.

Note that the current implementation has some issues, for example when there's nothing redrawing the screen (like a game), the rendering will appear to completely freeze (since cursor movements won't cause redraws).

kdl
debug {
    skip-cursor-only-updates-during-vrr
}

`deactivate-unfocused-windows`

Since: 25.08

Some clients (notably, Chromium- and Electron-based, like Teams or Slack) erroneously use the Activated xdg window state instead of keyboard focus for things like deciding whether to send notifications for new messages, or for picking where to show an IME popup.
Niri keeps the Activated state on unfocused workspaces and invisible tabbed windows (to reduce unwanted animations), surfacing bugs in these applications.

Set this debug flag to work around these problems.
It will cause niri to drop the Activated state for all unfocused windows.

kdl
debug {
    deactivate-unfocused-windows
}

`disable-10bit-output`

Since: next release

By default, niri will try to output a 10-bit color format to the monitor (before falling back to 8-bit).
However, this can currently cause problems on some Intel + NVIDIA mixed-GPU setups: the screen doesn't light up, or displays only white, etc.

Until this is fixed in Smithay, you can disable 10-bit color formats by setting this debug flag.

kdl
debug {
    disable-10bit-output
}

Key Bindings

These are not debug options, but rather key bindings.

`toggle-debug-tint`

Tints all surfaces green, unless they are being directly scanned out.

Useful to check if direct scanout is working.

kdl
binds {
    Mod+Shift+Ctrl+T { toggle-debug-tint; }
}

`debug-toggle-opaque-regions`

Since: 0.1.6

Tints regions marked as opaque with blue and the rest of the render elements with red.

Useful to check how Wayland surfaces and internal render elements mark their parts as opaque, which is a rendering performance optimization.

kdl
binds {
    Mod+Shift+Ctrl+O { debug-toggle-opaque-regions; }
}

`debug-toggle-damage`

Since: 0.1.6

Tints damaged regions with red.

kdl
binds {
    Mod+Shift+Ctrl+D { debug-toggle-damage; }
}

File: docs/wiki/Configuration:-Gestures.md

Overview

Since: 25.02

The gestures config section contains gesture settings.
For an overview of all niri gestures, see the Gestures wiki page.

Here's a quick glance at the available settings along with their default values.

kdl
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 {
        // off
        top-left
        // top-right
        // bottom-left
        // bottom-right
    }
}

`dnd-edge-view-scroll`

Scroll the tiling view when moving the mouse cursor against a monitor edge during drag-and-drop (DnD).
Also works on a touchscreen.

This will work for regular drag-and-drop (e.g. dragging a file from a file manager), and for window interactive move when targeting the tiling layout.

The options are:

  • trigger-width: size of the area near the monitor edge that will trigger the scrolling, in logical pixels.
  • delay-ms: delay in milliseconds before the scrolling starts.
    Avoids unwanted scrolling when dragging things across monitors.
  • max-speed: maximum scrolling speed in logical pixels per second.
    The scrolling speed increases linearly as you move your mouse cursor from trigger-width to the very edge of the monitor.
kdl
gestures {
    // Increase the trigger area and maximum speed.
    dnd-edge-view-scroll {
        trigger-width 100
        max-speed 3000
    }
}

`dnd-edge-workspace-switch`

Since: 25.05

Scroll the workspaces up/down when moving the mouse cursor against a monitor edge during drag-and-drop (DnD) while in the overview.
Also works on a touchscreen.

The options are:

  • trigger-height: size of the area near the monitor edge that will trigger the scrolling, in logical pixels.
  • delay-ms: delay in milliseconds before the scrolling starts.
    Avoids unwanted scrolling when dragging things across monitors.
  • max-speed: maximum scrolling speed; 1500 corresponds to one screen height per second.
    The scrolling speed increases linearly as you move your mouse cursor from trigger-width to the very edge of the monitor.
kdl
gestures {
    // Increase the trigger area and maximum speed.
    dnd-edge-workspace-switch {
        trigger-height 100
        max-speed 3000
    }
}

`hot-corners`

Since: 25.05

Put your mouse at the very top-left corner of a monitor to toggle the overview.
Also works during drag-and-dropping something.

off disables the hot corners.

kdl
// Disable the hot corners.
gestures {
    hot-corners {
        off
    }
}

Since: 25.11 You can choose specific hot corners by name: top-left, top-right, bottom-left, bottom-right.
If no corners are explicitly set, the top-left corner will be active by default.

kdl
// Enable the top-right and bottom-right hot corners.
gestures {
    hot-corners {
        top-right
        bottom-right
    }
}

You can also customize hot corners per-output in the output config.


File: docs/wiki/Configuration:-Include.md

Since: 25.11

You can include other files at the top level of the config.

kdl,must-fail
// Some settings...

include "colors.kdl"

// Some more settings...

Included files have the same structure as the main config file.
Settings from included files will be merged with the settings from the main config file.

Included config files can in turn include more files.
All included files are watched for changes, and the config live-reloads when any of them change.

You can include by filename or path.

  • Relative to the current file: other.kdl or ./other.kdl
  • By absolute path: /path/to/file.kdl
  • Since: 26.04 Home dir paths: ~/file.kdl expands to /home/user/file.kdl

Includes work only at the top level of the config:

kdl,must-fail
// All good: include at the top level.
include "something.kdl"

layout {
    // NOT allowed: include inside some other section.
    include "other.kdl"
}

Positionality

Includes are positional.
They will override options set prior to them.
Window rules from included files will be inserted at the position of the include line.
For example:

kdl
// colors.kdl
layout {
    border {
        active-color "green"
    }
}

overview {
    backdrop-color "green"
}
kdl,must-fail
// config.kdl
layout {
    border {
        active-color "red"
    }
}

// This overrides the border color and the backdrop color to green.
include "colors.kdl"

// This sets the overview backdrop color to red again.
overview {
    backdrop-color "red"
}

The end result:

  • the border color is green (from colors.kdl),
  • the overview backdrop color is red (it was set after colors.kdl).

Another example:

kdl
// rules.kdl
window-rule {
    match app-id="Alacritty"
    open-maximized false
}
kdl,must-fail
// config.kdl
window-rule {
    open-maximized true
}

// Window rules get inserted at this position.
include "rules.kdl"

window-rule {
    match app-id="firefox$"
    open-maximized true
}

This is equivalent to the following config file:

kdl
window-rule {
    open-maximized true
}

// Included from rules.kdl.
window-rule {
    match app-id="Alacritty"
    open-maximized false
}

window-rule {
    match app-id="firefox$"
    open-maximized true
}

Optional includes

Since: 26.04

By default, including a nonexistent file will cause an error.
You can allow nonexistent includes by setting optional=true:

kdl,must-fail
// Won't fail if this file doesn't exist.
include optional=true "optional-config.kdl"

// Regular include, will fail if the file doesn't exist.
include "required-config.kdl"

When an optional include file is missing, niri will emit a warning in the logs on every config reload.
This reminds you that the file is missing while still loading the config successfully.

The optional file is still watched for changes, so if you create it later, the config will automatically reload and apply the new settings.

Note that optional only affects whether a missing file causes an error.
If the file exists but contains invalid syntax or other errors, those errors will still cause a parsing failure.

Merging

Most config sections are merged between includes, meaning that you can set only a few properties, and only those properties will change.

kdl
// colors.kdl
layout {
    // Does not affect gaps, border width, etc.
    // Only changes colors as written.
    focus-ring {
        active-color "blue"
    }

    border {
        active-color "green"
    }
}
kdl,must-fail
// config.kdl
include "colors.kdl"

layout {
    // Does not set border and focus-ring colors,
    // so colors from colors.kdl are used.
    gaps 8

    border {
        width 8
    }
}

Multipart sections

Multipart sections like window-rule, output, or workspace are inserted as is without merging:

kdl
// laptop.kdl
output "eDP-1" {
    // ...
}
kdl,must-fail
// config.kdl
output "DP-2" {
    // ...
}

include "laptop.kdl"

// End result: both DP-2 and eDP-1 settings.

Binds

binds will override previously-defined conflicting keys:

kdl
// binds.kdl
binds {
    Mod+T { spawn "alacritty"; }
}
kdl,must-fail
// config.kdl
include "binds.kdl"

binds {
    // Overrides Mod+T from binds.kdl.
    Mod+T { spawn "foot"; }
}

Flags

Most flags can be disabled with false:

kdl
// csd.kdl

// Write "false" to explicitly disable.
prefer-no-csd false
kdl,must-fail
// config.kdl

// Enable prefer-no-csd in the main config.
prefer-no-csd

// Including csd.kdl will disable it again.
include "csd.kdl"

Non-merging sections

Some sections where the contents represent a combined structure are not merged.
Examples are struts, preset-column-widths, individual subsections in animations, pointing device sections in input.

kdl
// struts.kdl
layout {
    struts {
        left 64
        right 64
    }
}
kdl,must-fail
// config.kdl
layout {
    struts {
        top 64
        bottom 64
    }
}

include "struts.kdl"

// Struts are not merged.
// End result is only left and right struts.

Border special case

There's one special case that differs between the main config and included configs.

Writing layout { border {} } in an included config does nothing (since no properties are changed).
However, writing the same in the main config will enable the border, i.e. it's equivalent to layout { border { on; } }.

So, if you want to move your layout configuration from the main config to a separate file, remember to add on to the border section, for example:

kdl
// separate.kdl
layout {
    border {
        // Add this line:
        on

        width 4
        active-color "#ffc87f"
        inactive-color "#505050"
    }
}

The reason for this special case is that this is how it historically worked: back when I added borders, we didn't have any on flags, so I made writing the border {} section enable the border, with an explicit off to disable it.
It wouldn't be too problematic to change it, however the default config always had a pre-filled layout { border { off; } } section with a note saying that commenting out the off is enough to enable the border.
Many people likely have this part of the default config embedded in their configs now, so changing how it works would just cause a lot of confusion.


File: docs/wiki/Configuration:-Input.md

Overview

In this section you can configure input devices like keyboard and mouse, and some input-related options.

There's a section for each device type: keyboard, touchpad, mouse, trackpoint, trackball, tablet, touch.
Settings in those sections will apply to every device of that type.
Currently, there's no way to configure specific devices individually (but that is planned).

All settings at a glance:

text
/* Detailed source-code truncated for AI context efficiency. */

Keyboard

Layout

In the xkb section, you can set layout, variant, options, model and rules.
These are passed directly to libxkbcommon, which is also used by most other Wayland compositors.
See the xkeyboard-config(7) manual for more information.

kdl
input {
    keyboard {
        xkb {
            layout "us"
            variant "colemak_dh_ortho"
            options "compose:ralt,ctrl:nocaps"
        }
    }
}

[!TIP]

Since: 25.02

Alternatively, you can directly set a path to a .xkb file containing an xkb keymap.
This overrides all other xkb settings.

kdl
input {
    keyboard {
        xkb {
            file "~/.config/keymap.xkb"
        }
    }
}

[!NOTE]

Since: 25.08

If the xkb section is empty (like it is by default), niri will fetch xkb settings from systemd-localed at org.freedesktop.locale1 over D-Bus.
This way, for example, system installers can dynamically set the niri keyboard layout.
You can see this layout in localectl and change it with localectl set-x11-keymap, for example:

sh
$ localectl set-x11-keymap "us" "" "colemak_dh_ortho" "compose:ralt,ctrl:nocaps"
$ localectl
System Locale: LANG=en_US.UTF-8
               LC_NUMERIC=ru_RU.UTF-8
               LC_TIME=ru_RU.UTF-8
               LC_MONETARY=ru_RU.UTF-8
               LC_PAPER=ru_RU.UTF-8
               LC_MEASUREMENT=ru_RU.UTF-8
    VC Keymap: us-colemak_dh_ortho
   X11 Layout: us
  X11 Variant: colemak_dh_ortho
  X11 Options: compose:ralt,ctrl:nocaps

By default, localectl will set the TTY keymap to the closest match of the XKB keymap.
You can prevent that with a --no-convert flag, for example: localectl set-x11-keymap --no-convert "us,ru".

These settings are picked up by some other programs too, like GDM.

When using multiple layouts, niri can remember the current layout globally (the default) or per-window.
You can control this with the track-layout option.

  • global: layout change is global for all windows.
  • window: layout is tracked for each window individually.
kdl
input {
    keyboard {
        track-layout "global"
    }
}

Repeat

Delay is in milliseconds before the keyboard repeat starts.
Rate is in characters per second.

kdl
input {
    keyboard {
        repeat-delay 600
        repeat-rate 25
    }
}

Num Lock

Since: 25.05

Set the numlock flag to turn on Num Lock automatically at startup.

You might want to disable (comment out) numlock if you're using a laptop with a keyboard that overlays Num Lock keys on top of regular keys.

kdl
input {
    keyboard {
        numlock
    }
}

Pointing Devices

Most settings for the pointing devices are passed directly to libinput.
Other Wayland compositors also use libinput, so it's likely you will find the same settings there.
For flags like tap, omit them or comment them out to disable the setting.

A few settings are common between input devices:

  • off: if set, no events will be sent from this device.

A few settings are common between touchpad, mouse, trackpoint, and trackball:

  • natural-scroll: if set, inverts the scrolling direction.
  • accel-speed: pointer acceleration speed, valid values are from -1.0 to 1.0 where the default is 0.0.
  • accel-profile: can be adaptive (the default) or flat (disables pointer acceleration).
  • scroll-method: when to generate scroll events instead of pointer motion events, can be no-scroll, two-finger, edge, or on-button-down.
    The default and supported methods vary depending on the device type.
  • scroll-button: Since: 0.1.10 the button code used for the on-button-down scroll method. You can find it in libinput debug-events.
  • scroll-button-lock: Since: 25.08 when enabled, the button does not need to be held down. Pressing once engages scrolling, pressing a second time disengages it, and double click acts as single click of the the underlying button.
  • left-handed: if set, changes the device to left-handed mode.
  • middle-emulation: emulate a middle mouse click by pressing left and right mouse buttons at once.

Settings specific to touchpads:

  • tap: tap-to-click.
  • dwt: disable-when-typing.
  • dwtp: disable-when-trackpointing.
  • drag: Since: 25.05 can be true or false, controls if tap-and-drag is enabled.
  • drag-lock: Since: 25.02 if set, lifting the finger off for a short time while dragging will not drop the dragged item. See the libinput documentation.
  • tap-button-map: can be left-right-middle or left-middle-right, controls which button corresponds to a two-finger tap and a three-finger tap.
  • click-method: can be button-areas or clickfinger, changes the click method.
  • disabled-on-external-mouse: do not send events while external pointer device is plugged in.

Settings specific to touchpad and mouse:

  • scroll-factor: Since: 0.1.10 scales the scrolling speed by this value.

    Since: 25.08 You can also override horizontal and vertical scroll factor separately like so: scroll-factor horizontal=2.0 vertical=-1.0

Settings specific to tablet and touch:

Tablets and touchscreens are absolute pointing devices that can be mapped to a specific output like so:

kdl
input {
    tablet {
        map-to-output "eDP-1"
    }

    touch {
        map-to-output "eDP-1"
    }
}

Valid output names are the same as the ones used for output configuration.

Since: 0.1.7 When a tablet is not mapped to any output, it will map to the union of all connected outputs, without aspect ratio correction.

Settings specific to tablet:

  • map-to-focused-output: Since: 26.04 will map the tablet to the focused output, takes precedence over map-to-output.

  • map-to-focused-window: Since: next release will map the tablet to the focused window's geometry, takes precedence over map-to-focused-output and map-to-output.
    Falls back to those when no window is focused (for example, in the overview).

    When the tablet is also mapped to a specific output via map-to-output, the map-to-focused-window flag will map the tablet to the active window on that output.
    If the tablet isn't mapped to any specific output, it will map the tablet to the current focused window regardless of where it is.

General Settings

These settings are not specific to a particular input device.

`disable-power-key-handling`

By default, niri will take over the power button to make it sleep instead of power off.
Set this if you would like to configure the power button elsewhere (i.e. logind.conf).

kdl
input {
    disable-power-key-handling
}

`warp-mouse-to-focus`

Makes the mouse warp to newly focused windows.

Does not make the cursor visible if it had been hidden.

kdl
input {
    warp-mouse-to-focus
}

By default, the cursor warps separately horizontally and vertically.
I.e. if moving the mouse only horizontally is enough to put it inside the newly focused window, then the mouse will move only horizontally, and not vertically.

Since: 25.05 You can customize this with the mode property.

  • mode="center-xy": warps by both X and Y coordinates together.
    So if the mouse was anywhere outside the newly focused window, it will warp to the center of the window.
  • mode="center-xy-always": warps by both X and Y coordinates together, even if the mouse was already somewhere inside the newly focused window.
kdl
input {
    warp-mouse-to-focus mode="center-xy"
}

`focus-follows-mouse`

Focuses windows and outputs automatically when moving the mouse over them.

kdl
input {
    focus-follows-mouse
}

Since: 0.1.8 You can optionally set max-scroll-amount.
Then, focus-follows-mouse won't focus a window if it will result in the view scrolling more than the set amount.
The value is a percentage of the working area width.

kdl
input {
    // Allow focus-follows-mouse when it results in scrolling at most 10% of the screen.
    focus-follows-mouse max-scroll-amount="10%"
}
kdl
input {
    // Allow focus-follows-mouse only when it will not scroll the view.
    focus-follows-mouse max-scroll-amount="0%"
}

`workspace-auto-back-and-forth`

Normally, switching to the same workspace by index twice will do nothing (since you're already on that workspace).
If this flag is enabled, switching to the same workspace by index twice will switch back to the previous workspace.

Niri will correctly switch to the workspace you came from, even if workspaces were reordered in the meantime.

kdl
input {
    workspace-auto-back-and-forth
}

`mod-key`, `mod-key-nested`

Since: 25.05

Customize the Mod key for key bindings.
Only valid modifiers are allowed, e.g. Super, Alt, Mod3, Mod5, Ctrl, Shift.

By default, Mod is equal to Super when running niri on a TTY, and to Alt when running niri as a nested winit window.

[!NOTE]
There are a lot of default bindings with Mod, none of them "make it through" to the underlying window.
You probably don't want to set mod-key to Ctrl or Shift, since Ctrl is commonly used for app hotkeys, and Shift is used for, well, regular typing.

kdl
// Switch the mod keys around: use Alt normally, and Super inside a nested window.
input {
    mod-key "Alt"
    mod-key-nested "Super"
}

File: docs/wiki/Configuration:-Introduction.md

Per-Section Documentation

You can find documentation for various sections of the config on these wiki pages:

Loading

Niri will load configuration from $XDG_CONFIG_HOME/niri/config.kdl or ~/.config/niri/config.kdl, falling back to /etc/niri/config.kdl.
If both of these files are missing, niri will create $XDG_CONFIG_HOME/niri/config.kdl with the contents of the default configuration file, which are embedded into the niri binary at build time.
Please use the default configuration file as the starting point for your custom configuration.

The configuration is live-reloaded.
Simply edit and save the config file, and your changes will be applied.
This includes key bindings, output settings like mode, window rules, and everything else.

You can run niri validate to parse the config and see any errors.

To use a different config file path, pass it in the --config or -c argument to niri.

You can also set $NIRI_CONFIG to the path of the config file.
--config always takes precedence.
If --config or $NIRI_CONFIG doesn't point to a real file, the config will not be loaded.
If $NIRI_CONFIG is set to an empty string, it is ignored and the default config location is used instead.

Syntax

The config is written in KDL.

Comments

Lines starting with // are comments; they are ignored.

Also, you can put /- in front of a section to comment out the entire section:

kdl
/-output "eDP-1" {
    // Everything inside here is ignored.
    // The display won't be turned off
    // as the whole section is commented out.
    off
}

Flags

Toggle options in niri are commonly represented as flags.
Writing out the flag enables it, and omitting it or commenting it out disables it.
For example:

kdl
// "Focus follows mouse" is enabled.
input {
    focus-follows-mouse

    // Other settings...
}
kdl
// "Focus follows mouse" is disabled.
input {
    // focus-follows-mouse

    // Other settings...
}

Sections

Most sections cannot be repeated. For example:

kdl
// This is valid: every section appears once.
input {
    keyboard {
        // ...
    }

    touchpad {
        // ...
    }
}
kdl,must-fail
// This is NOT valid: input section appears twice.
input {
    keyboard {
        // ...
    }
}

input {
    touchpad {
        // ...
    }
}

Exceptions are, for example, sections that configure different devices by name:

kdl
output "eDP-1" {
    // ...
}

// This is valid: this section configures a different output.
output "HDMI-A-1" {
    // ...
}

// This is NOT valid: "eDP-1" already appeared above.
// It will either throw a config parsing error, or otherwise not work.
output "eDP-1" {
    // ...
}

Defaults

Omitting most of the sections of the config file will leave you with the default values for that section.
A notable exception is binds {}: they do not get filled with defaults, so make sure you do not erase this section.

Breaking Change Policy

As a rule, niri updates should not break existing config files.
(For example, the default config from niri v0.1.0 still parses fine on v25.02 as I'm writing this.)

Exceptions can be made for parsing bugs.
For example, niri used to accept multiple binds to the same key, but this was not intended and did not do anything (the first bind was always used).
A patch release changed niri from silently accepting this to causing a parsing failure.
This is not a blanket rule, I will consider the potential impact of every breaking change like this before deciding to carry on with it.

Keep in mind that the breaking change policy applies only to niri releases.
Commits between releases can and do occasionally break the config as new features are ironed out.
However, I do try to limit these, since several people are running git builds.