nwg-dock
August 15, 2026 · View on GitHub
A macOS-style dock for Hyprland and Sway, written in Rust.
Renamed from nwg-dock-hyprland. The Rust port supports both Hyprland and Sway through one binary (Compositor trait + runtime --wm auto-detection), so the compositor-specific name didn't fit anymore. Existing users: make install installs a nwg-dock-hyprland → nwg-dock symlink so your exec-once = nwg-dock-hyprland … autostart line keeps working. See Migrating from nwg-dock-hyprland below.
Ported from nwg-piotr/nwg-dock-hyprland (Hyprland-only Go) and informed by nwg-piotr/nwg-dock (Sway-only Go), with enhancements the Go versions don't have.
Features
- Multi-monitor — dock appears on all monitors simultaneously
- Multi-compositor — Hyprland and Sway via auto-detection (override with
--wm) - Content-width — floats centered at screen edge, sized to its icons
- Auto-hide — compositor IPC cursor tracking with configurable timeout
- Drag-to-reorder — drag any pinned icon (running or not) to rearrange
- Drag-to-remove — drag an icon off the dock to unpin it (like macOS)
- Dock settings menu — right-click dock background to lock/unlock arrangement
- Configurable opacity —
--opacity 0-100for translucent or opaque dock - Right-click menus — pin/unpin, close, toggle floating, fullscreen, move to workspace
- Launch animation — optional bounce animation on dock icons while an app is starting (
--launch-animation) - Workspace switcher — optional pill-button row between pinned and tasks for jumping workspaces (
--ws, default off) - Middle-click — launch new instance of any running app
- Monitor hotplug — dock windows reconcile automatically when monitors are added/removed
- Rotated/scaled monitors — cursor tracking works correctly with portrait and scaled displays
- Icon scaling — icons shrink automatically when many apps are open
- Instant pin sync — inotify-based, shared with
nwg-drawer - Kill-proof — ignores compositor close requests (Hyprland
killactive/ Super+Q) so the dock can't be accidentally closed; usemake stoporpkill -f nwg-dockto stop it intentionally - Go flag compatibility — accepts original Go nwg-dock-hyprland flag names
Install
Requirements
- Rust 1.97 or later (pinned in
rust-toolchain.toml; rustup picks it up automatically) - GTK4 and gtk4-layer-shell system libraries
- A supported compositor: Hyprland or Sway — on other Wayland compositors (Niri, river, etc.) the dock starts in degraded mode: pinned apps still render and click-to-launch still works, but live features (auto-hide, workspace switcher, event-driven rebuilds) are disabled. A warning logs to stderr/journal so you know you're running degraded.
Install system dependencies
# Arch Linux
sudo pacman -S gtk4 gtk4-layer-shell
# Ubuntu/Debian
sudo apt install libgtk-4-dev libgtk4-layer-shell-dev
# Fedora
sudo dnf install gtk4-devel gtk4-layer-shell-devel
From crates.io (recommended for end users)
cargo install nwg-dock
Lands the binary at ~/.cargo/bin/nwg-dock. The default style sheet is created on first run by copying from the system data dir if available, otherwise the dock falls back to its embedded GTK4 defaults — so the binary alone is enough to launch the dock; you only need make install if you want the bundled style.css and the nwg-dock-hyprland legacy symlink.
Migrating from nwg-dock-hyprland via cargo install: the legacy symlink alias is a make install feature only — cargo install ships the nwg-dock binary alone. Update your autostart line to nwg-dock … (drop the -hyprland suffix).
make install — for source builds, distro packagers, and the legacy symlink
The Makefile install path drops the binary, the bundled style.css, and the nwg-dock-hyprland legacy symlink. Three invocations depending on where the binary should land:
Default — system-wide (needs sudo):
sudo make install
Writes:
nwg-dock→/usr/local/bin/nwg-dock- Legacy symlink →
/usr/local/bin/nwg-dock-hyprland(so old autostart lines keep working) - Data files →
/usr/local/share/nwg-dock-hyprland/(path kept on the Go-predecessor convention so existing users'~/.config/nwg-dock-hyprland/style.csscustomizations keep resolving — a full-rename migration is planned for a later minor)
No-sudo, dev workflow (useful when working from a clone):
make install PREFIX=$HOME/.local BINDIR=$HOME/.cargo/bin
Distro-parity (matches Go upstream's /usr/bin exactly):
sudo make install PREFIX=/usr
Usage
# Basic — auto-hide, 48px icons, translucent
nwg-dock -d -i 48 --mb 10 --hide-timeout 400 --opacity 75
# With launch animation and drawer integration
nwg-dock -d -i 48 --mb 10 --hide-timeout 400 --opacity 75 --launch-animation -c "nwg-drawer --pb-auto"
# With workspace switcher row (between pinned and tasks)
nwg-dock -d -i 48 --mb 10 --ws --num-ws 5
# Force Sway backend (auto-detection is usually enough)
nwg-dock --wm sway
Configuration file
In addition to CLI flags, nwg-dock reads a TOML config file at:
$XDG_CONFIG_HOME/nwg-dock-hyprland/config.toml
(falling back to ~/.config/nwg-dock-hyprland/config.toml if XDG_CONFIG_HOME is unset). Override the path with --config <PATH>. The nwg-dock-hyprland directory name is shared with the existing style.css for continuity with the Go-era setup.
A commented example with every field documented is installed alongside the CSS:
cp /usr/local/share/nwg-dock-hyprland/config.example.toml \
~/.config/nwg-dock-hyprland/config.toml
$EDITOR ~/.config/nwg-dock-hyprland/config.toml
(Adjust the source prefix if you used make install PREFIX=$HOME/.local … — the example file ends up in $PREFIX/share/nwg-dock-hyprland/.)
Precedence: CLI flags > config file > built-in defaults. Anything you pass on the command line wins, regardless of what the file says — so --icon-size 32 on the autostart line still takes effect even if the file specifies icon-size = 64.
Hot-reload: Most fields apply immediately on save — the dock fires a desktop notification confirming the reload (or reporting a parse error). The following fields require the dock to be restarted to take effect:
multi,wm,autohide,resident,hotspot-layer,layer,exclusive,position,full
The dock surfaces a "change applies on next restart" notification when one of those is edited.
Inspect what's loaded:
nwg-dock --print-config
Dumps the currently-effective merged config (CLI + file + defaults) in TOML form — handy for verifying which value won. The output shows resolved values only, not per-field provenance; if you need to know whether a field came from the CLI vs the file, point --config at a non-existent path so the file layer is skipped:
nwg-dock --config /tmp/nope.toml --print-config
That snapshot reflects CLI + defaults only; diff it against nwg-dock --print-config (which loads your real file) to see which fields the file is overriding. Doesn't start the dock; safe to run alongside a running instance.
Schema: see data/nwg-dock-hyprland/config.example.toml for the full sectioned schema ([behavior], [layout], [appearance], [launcher], [filters]).
Example: shrink the autostart line. A typical autostart entry like:
exec-once = nwg-dock -d -i 48 --mb 10 --hide-timeout 400 --opacity 75 --launch-animation -c "nwg-drawer --opacity 88 --pb-auto"
becomes:
exec-once = nwg-dock -d -c "nwg-drawer --opacity 88 --pb-auto"
with the persistable bits moved into ~/.config/nwg-dock-hyprland/config.toml:
[appearance]
icon-size = 48
opacity = 75
launch-animation = true
[layout]
mb = 10
[behavior]
hide-timeout = 400
The -d (autohide) and -c (launcher command) stay on the CLI because launcher is invocation-specific and autohide is one of the restart-required fields — you might want to swap autohide on/off with a different exec-once line for testing.
Compositor setup
# Print Hyprland autostart snippets
make setup-hyprland
# Print Sway autostart snippets
make setup-sway
Hyprland autostart example
If you use uwsm for systemd-scoped session management (the typical Arch+Hyprland setup):
# ~/.config/hypr/autostart.conf
exec-once = uwsm-app -- nwg-dock -d -i 48 --mb 10 --hide-timeout 400 --opacity 75 --launch-animation -c "nwg-drawer --opacity 88 --pb-auto"
Without uwsm (Slackware, distros where uwsm isn't packaged, or by preference) — drop the uwsm-app -- prefix:
# ~/.config/hypr/autostart.conf
exec-once = nwg-dock -d -i 48 --mb 10 --hide-timeout 400 --opacity 75 --launch-animation -c "nwg-drawer --opacity 88 --pb-auto"
Hyprland Lua config (Omarchy 4.0 "Quattro" and other Lua setups)
Hyprland 0.55+ Lua configurations don't read autostart.conf. On Omarchy
4.0 the equivalent lives in ~/.config/hypr/autostart.lua:
-- ~/.config/hypr/autostart.lua
o.launch_on_start([[nwg-dock -d -i 48 --mb 10 --hide-timeout 400 --opacity 75 --launch-animation -c "nwg-drawer --opacity 88 --pb-auto"]])
On plain Lua setups without Omarchy's helpers, register the command on the
start hook instead. This goes in a Lua file loaded by
~/.config/hypr/hyprland.lua (directly, or via a require(...) from it):
hl.on("hyprland.start", function()
hl.exec_cmd([[nwg-dock -d -i 48 --mb 10 --hide-timeout 400 --opacity 75 --launch-animation -c "nwg-drawer --pb-auto"]])
end)
Migrating to Omarchy 4.0: the Quattro migration generates the new
.lua config files but does not carry custom exec-once lines
across from autostart.conf — after the upgrade the dock (and anything
else you autostarted) silently stops launching until you re-add it in
autostart.lua as above. The dock itself is fully functional on Lua
sessions as of v0.6.0+ (window actions use the Lua dispatcher syntax
automatically via nwg-common 0.7).
The dock has no runtime dependency on uwsm or systemd; the wrapper just buys you per-process cgroup tracking and clean teardown on logout if you want it.
Known issue: GTK4 crash on DPMS cycles (Hyprland ≥ 0.56)
GTK ≤ 4.22 has a bug in its Wayland dmabuf-feedback handler
(gdk/wayland/gdkdmabuf-wayland.c munmaps the wrong pointer when the
compositor re-sends zwp_linux_dmabuf_feedback_v1). Hyprland 0.56 started
re-sending that feedback every time an output is disabled/re-enabled, so any
GTK4 client — the dock included — can segfault in
wl_display_dispatch_queue_pending after a DPMS off/on cycle. Whether a given
cycle crashes depends on heap-allocation alignment, so it strikes
intermittently.
Until a fixed GTK ships, launch the dock with the dmabuf protocol disabled:
# ~/.config/hypr/autostart.conf
exec-once = uwsm-app -- env GDK_WAYLAND_DISABLE=zwp_linux_dmabuf_v1 nwg-dock -d -i 48 --mb 10 --hide-timeout 400 --opacity 75 --launch-animation -c "nwg-drawer --opacity 88 --pb-auto"
The dock doesn't use dmabuf texture import or graphics offload, so the only effect of the switch is dodging the crash. The same prefix protects any other GTK4 layer-shell client that dies the same way after screen blanking.
Signal control
# Toggle visibility
pkill -f -35 nwg-dock # SIGRTMIN+1
# Show
pkill -f -36 nwg-dock # SIGRTMIN+2
# Hide
pkill -f -37 nwg-dock # SIGRTMIN+3
Theming
The dock loads CSS from ~/.config/nwg-dock-hyprland/style.css (path kept for continuity with the Go predecessor). Changes are picked up instantly via live file-change detection — no restart or signal needed. Hot-reload follows the full @import graph, so theme managers like tinty or stylix work out of the box.
Override the path with -s /path/to/custom.css.
Three CSS layers are stacked, highest priority last:
- Embedded defaults — compact button sizing, indicator spacing, etc.
- Programmatic overrides —
--opacityand bounce animation keyframes - Your CSS file — always wins
Selected classes available for overriding:
.dock-workspace-row— container for the workspace button row (when--wsis set).dock-workspace-button— individual workspace button.dock-workspace-active— class added to the currently-focused workspace's button
base16 themes via tinty
tinty + tinted-nwg-dock templates retheme the dock live. See the tinted-nwg-dock README for setup; apply a theme with:
tinty apply base16-tokyo-night-dark
Migrating from nwg-dock-hyprland
If you're coming from either the Go nwg-dock-hyprland or an older Rust build where the binary was called nwg-dock-hyprland:
- Installed via
make install— nothing to do. Thenwg-dock-hyprlandsymlink is installed alongside the newnwg-dockbinary, soexec-once = nwg-dock-hyprland …keeps working. - Installed via
cargo install— update your autostart tonwg-dock …(or invokenwg-dockdirectly).cargo installdoesn't create the symlink.
The preferred canonical command going forward is nwg-dock. The nwg-dock-hyprland symlink is deprecated and will be removed in a future minor release; CHANGELOGs will give advance notice.
Shared pin file
Pin state lives at ~/.cache/mac-dock-pinned, shared with nwg-drawer. Pin an app from either side (dock: right-click → Pin; drawer: right-click). Drag icons in the dock to reorder; drag off to unpin.
Contributing
PRs welcome. main is protected — open from a feature branch. Run make lint (fmt + clippy + test + deny + audit) locally before requesting review. CI runs the equivalent checks across separate workflows plus CodeRabbit.
User-visible PRs add a CHANGELOG bullet under ## [x.y.z] — Unreleased in CHANGELOG.md, following Keep a Changelog.
Deviations from Go nwg-dock-hyprland
- Multi-compositor — Go version is Hyprland-only; Rust port supports both via
nwg-common's Compositor trait. - Shared pin file — Go dock uses
~/.cache/nwg-dock-pinned; Rust port shares~/.cache/mac-dock-pinnedwith the drawer for instant two-way sync. - Per-monitor windows — Go creates one window; Rust creates one per monitor for better multi-monitor support.
- Smart rebuild — Go force-rebuilds on every active-window event; Rust rebuilds only when the client list or active window actually changes.
- Drag-to-reorder — new feature not in the Go dock.
- CLI flag naming — multi-word flags standardized to kebab-case. Multi-char Go short forms (
-hd,-iw,-is) not available; use the long forms. - Fuzzy class matching — desktop file
github-desktopvs compositor classgithub desktopare matched automatically. - Workspace switcher defaults OFF. The Go dock shows the workspace button row by default and provides
-nowsto hide it; we default the row OFF and provide--wsto enable. Existing dock users updating across this version line see no UI change unless they opt in.
Credits
Ported from nwg-piotr/nwg-dock-hyprland (MIT), informed by nwg-piotr/nwg-dock (MIT).
License
MIT. See LICENSE.