A Quickshell desktop shell for niri. One frosted strip is fused to the top edge of each screen; its ends curve down into rounded screen corners. Every panel grows out of the bar's bottom edge, joined by concave fillets so the two read as one liquid surface. Panels near a screen edge snap to it and run down the side like a drawer. Along the bar's edge runs the horizon, a thin aurora line that carries on around every panel and gathers light: under the cursor, where panels join, in a sun that crosses the screen once a day, and in a comet that shoots across when a notification arrives.
| Part | Contents |
|---|---|
| left | start badge + app launcher, niri workspaces of this output (the active one as a live minimap of its columns), its active window |
| middle | clock/date (digits roll over) → dashboard |
| right | now playing (cover in a progress ring, equalizer, controls) · tray · bluetooth, network (name on hover), volume, battery · notifications · power |
Every widget has a panel: media controls + seek + player switcher, a dashboard, a notification center (with do-not-disturb), the network (wired, VPN and Wi-Fi; see below), and audio (output/input sliders and device pickers). The shell is also the notification daemon: toasts drop out of the bar's top-right corner as a drawer. The launcher and clipboard history grow out of the middle of the bar.
device battery, disconnect and forget buttons (forget asks for a second click). device battery, forget (right-click).
- Network: Wi-Fi join form with password reveal and inline errors, enterprise (802.1X: PEAP/TTLS, identity, domain, CA) and hidden networks ("Add network…"), forget for saved networks. VPN on/off for Mullvad (its CLI) and NetworkManager VPN/WireGuard profiles, with a shield on the bar icon while one is up.
- Clipboard: history via cliphist (the shell runs the
wl-paste --watchrecorders), a searchable panel with image thumbnails, Enter copies, Del removes. - Screenshots: niri's own tools (region/screen/window from the start menu or IPC); the shell shows a toast with a thumbnail and Open / Show folder.
- Audio: the volume popup has output/input device pickers and a per-app mixer (a slider per playing app; click its icon to mute). While an app records from the microphone, a red mic shows next to the volume and the popup names the app.
- OSD: volume/brightness drops out of the middle of the bar on the focused screen for any change (keys, apps).
- Idle: lock and screen-off timers, respecting apps that inhibit idle; "Awake" toggle.
- Dashboard: click the clock. A greeting with a giant clock, uptime and sunrise/sunset; weather now, the next hours as a temperature curve, the next 6 days; the calendar; now playing; CPU, memory, disk and CPU temperature gauges (polled only while it's open). The lock screen shows the current weather under the date.
- Weather: Open-Meteo (no API key), cached in
~/.cache/quickshell/by-shell/yuki/weather.jsonand refreshed every 30 min while in use. Set a city orlat,lonin Settings → Bar; left empty, the location comes from your IP via ipinfo.io. Nothing is fetched until the dashboard or lock screen needs it. - Window switcher: Alt+Tab over the windows in most-recently-used order (Tab / Shift+Tab / arrows, release Alt to switch, Esc cancels), in three looks to pick from in Appearance: cards, list, or an island under the bar. App icons for now; live previews wait on window capture in niri. Your niri binds take precedence over niri's own switcher, so binding Alt+Tab (below) is all it needs.
- Power: power-profiles-daemon picker (power menu, battery popup, settings).
- Polkit: yuki is the authentication agent (pkexec, GNOME Disks, …): a centered dialog on the focused screen with the action, the identity (click to switch when there are several admins) and a password field. If another agent already runs, yuki logs a warning and leaves it alone.
- App colors: niri, kitty and GTK snippets in
~/.local/state/yuki/theme/(see App colors), plus a blurred wallpaper for niri's overview backdrop.
The distro icon opens the start menu:
- Header: avatar, user@host and uptime, with buttons for settings, lock and power.
- Pinned apps: right-click a tile to unpin.
- Frequent apps: the ones you launch most.
- Wallpaper card: previous, next and shuffle for that screen.
- Quick toggles: Dark/Light, Dynamic colors, Silent (do not disturb).
The grid icon next to it opens the launcher. Right-click an app there to pin it; with an empty search, apps are sorted by how often you launch them.
Settings (gear in the start menu, or ipc call settings open [page]):
- Appearance: color source, dark/light mode, which screen's wallpaper the colors come from, Material scheme (with live previews), Catppuccin flavor and accent, blur and opacity.
- Wallpaper: management on/off, folder, per-screen or all-screen picker, shuffle.
- Bar: clock format, date, media and active-window widgets.
- Notifications: do not disturb, toast duration, clear all.
- Start menu: reorder and unpin pinned apps.
- About: paths and reload.
The shell draws the wallpaper itself, one per output, with a fade-and-settle
transition. Outputs without their own wallpaper use the default (or the first
image in the folder, ~/wallpapers by default).
Turn off Manage wallpapers in Settings → Wallpaper to let another app manage
the compositor background. This removes yuki's desktop wallpapers and overview
backdrops immediately. Choices are preserved and still used for the lock screen
and dynamic colors; turning management back on restores them. The setting
wallpaperEnabled defaults to true and persists across restarts.
With the color source set to wallpaper, the seed color of one output's wallpaper
(the output at 0,0 unless set otherwise) is extracted with Google's
material-color-utilities (vendored in lib/mcu, run in a WorkerScript) and turned
into a Material You scheme. tonal-spot is the same scheme noctalia's
m3-tonal-spot uses, so you get matching colors. Seeds are cached per image in
the settings file.
The power button opens tiles for Lock, Suspend, Log out (E), Restart
and Shut down (P). Log out, restart and shut down need a second press within 4 s.
Suspend locks first and suspends once every output is covered. So does any other
suspend (lid, systemctl suspend): yuki holds a logind delay inhibitor until the
lock screen is up ("Lock before sleep" in System settings). loginctl lock-session
and unlock-session lock and unlock it too.
The lock screen is a real ext-session-lock. Each output's wallpaper blurs in
when it locks and back out on unlock. If the shell crashes while locked, it locks
again as soon as it restarts. It shows a clock, your avatar (~/.face.icon), and a password
field authenticated through PAM (pam/password.conf, plain pam_unix). Also:
now-playing controls, the niri keyboard layout, and suspend/restart/shut down.
yuki ipc call lock preview shows the same UI without locking (Esc leaves).
Use it to tweak the design safely.
If the shell ever dies while locked, niri keeps the session locked (by design). Recover from a TTY (Ctrl+Alt+F2):
WAYLAND_DISPLAY=wayland-1 yuki & # start a new instance
WAYLAND_DISPLAY=wayland-1 yuki ipc call lock lockand unlock normally on the new lock screen.
For idle locking, have swayidle/hypridle call yuki ipc call lock lock.
inputs.yuki.url = "github:zaroc-dev/yuki";| Output | What |
|---|---|
packages.<system>.yuki (default) |
yuki = qs -p <config> with cliphist, wl-clipboard, brightnessctl, xdg-utils on PATH. yuki ipc call … talks to it. .override { configDir = "/path"; } runs a checkout instead of the store copy |
packages.<system>.sddm-theme |
.override { background = ./wall.jpg; settings = { accent = "#…"; }; } |
packages.<system>.plymouth-theme |
.override { accent = …; base = …; surface = …; text = …; } |
homeManagerModules.default |
programs.yuki = { enable; configDir; systemd.enable; gtk.enable; } |
nixosModules.default |
programs.yuki = { sddm = { enable; background; }; plymouth.enable; palette = { … }; } |
For hot reload, point configDir at a checkout (e.g. an out-of-store
symlink): the shell then runs the live QML, and settings are shared with the
packaged build because the shell id is pinned (//@ pragma ShellId yuki).
# home-manager
imports = [ inputs.yuki.homeManagerModules.default ];
programs.yuki = {
enable = true;
configDir = "${config.home.homeDirectory}/source/shell";
};
# nixos
imports = [ inputs.yuki.nixosModules.default ];
programs.yuki = {
sddm = { enable = true; background = ./wallpapers/raiden.shogun.jpg; };
plymouth.enable = true;
};Needs niri, NetworkManager, PipeWire, UPower, BlueZ, power-profiles-daemon,
and the fonts Inter and JetBrainsMono Nerd Font (the home-manager module
installs the fonts).
nix run github:zaroc-dev/yuki # try it
nix run nixpkgs#quickshell -- -p ~/source/shell # from a checkout (hot reload)In niri's config.kdl:
spawn-at-startup "yuki"and in keybinds.kdl:
Mod+Space hotkey-overlay-title="App Launcher" { spawn "yuki" "ipc" "call" "launcher" "toggle"; }
Mod+N hotkey-overlay-title="Notifications" { spawn "yuki" "ipc" "call" "bar" "toggle" "notifications"; }
Super+Alt+L hotkey-overlay-title="Lock" { spawn "yuki" "ipc" "call" "lock" "lock"; }
Alt+Tab hotkey-overlay-title="Switch Windows" { spawn "yuki" "ipc" "call" "switcher" "next"; }
Alt+Shift+Tab { spawn "yuki" "ipc" "call" "switcher" "prev"; }If another notification daemon is running, it owns org.freedesktop.Notifications
and yuki shows no toasts until it exits; then yuki takes the name over.
| Where | Action |
|---|---|
| start button | click: start menu · right-click: cycle themes |
| launcher button | click: launcher |
| media | click: popup · middle: play/pause |
| workspaces | click: focus · minimap tile: focus that window · scroll: up/down |
| notifications | click: center · right-click: toggle do-not-disturb |
| volume | click: popup · scroll: ±5% · middle: mute |
| tray item | click: activate · right-click: menu · middle: secondary |
| power | click: power menu (keys L S E R P inside) |
| launcher | type to filter · ↑/↓/Tab · Enter launches · Esc closes |
yuki ipc show
yuki ipc call launcher toggle|open|close
yuki ipc call switcher next|prev|close · style cards|list|island
yuki ipc call bar toggle media|dashboard|notifications|network|volume|power
yuki ipc call lock lock|preview|closePreview|isLocked
yuki ipc call vpn toggle|on|off <name or ""> · status
yuki ipc call settings open [appearance|wallpaper|bar|notifications|start|about]
yuki ipc call wallpaper enable true|false · toggle
yuki ipc call wallpaper set <output|all> <path>
yuki ipc call wallpaper random <output> · shuffle · get <output>
yuki ipc call theme source wallpaper|catppuccin|custom
yuki ipc call theme mode dark|light
yuki ipc call theme scheme tonal-spot|content|expressive|fidelity|fruit-salad|monochrome|neutral|rainbow|vibrant
yuki ipc call clipboard toggle|clear
yuki ipc call screenshot region|screen|window
yuki ipc call audio up <step>|down <step>|mute|micMute
yuki ipc call media toggle|next|previous
yuki ipc call brightness up|down|set <percent>
yuki ipc call idle toggleKeepAwake
yuki ipc call bar toggle start|media|dashboard|notifications|bluetooth|network|volume|battery|power
yuki ipc call shell reload [true = hard]
yuki ipc call power run lock|suspend|logout|reboot|poweroff
yuki ipc call theme flavor mocha|macchiato|frappe|latte|custom
yuki ipc call theme accent mauve|blue|pink|…
yuki ipc call theme cycle
yuki ipc call notifications toggleDnd|clearThe network popup lists NetworkManager VPN/WireGuard profiles and Mullvad.
OpenConnect profiles launch browser authentication in the desktop session, then
NetworkManager manages the tunnel without a terminal. Toggle off to disconnect,
or to cancel a pending login. Session cookies are passed in memory and are not
saved in the profile. Install the corresponding NetworkManager VPN plugin on
the host (for NixOS, networking.networkmanager.plugins).
All colors come from config/Theme.qml. Whatever the source, it exposes the same
Catppuccin-shaped palette, so every widget works with every theme. Settings
persist in ~/.local/state/quickshell/by-shell/yuki/settings.json. The file is
watched, so editing it by hand restyles the shell live.
themeSource: "wallpaper": Material You from the wallpaper (scheme,themeMode,colorScreen)themeSource: "catppuccin":flavor+accent, with light mode = LattethemeSource: "custom": Mocha overridden by any keys incustom
blur uses niri's ext-background-effect. With it off, surfaces fall back to
near-opaque.
Whenever the palette changes, yuki writes color files to ~/.local/state/yuki/theme/
and nudges niri and kitty to reload. Include them from your configs (absolute paths):
// niri config.kdl
include optional=true "/home/you/.local/state/yuki/theme/niri.kdl"# kitty.conf
include /home/you/.local/state/yuki/theme/kitty.confGTK 3/4 gtk.css are pointed at gtk3.css / gtk4.css by the home-manager module
(programs.yuki.gtk.enable, on by default). colors.json has the full Material
palette for anything else.
shell.qml root: wallpapers, bars, toasts, launcher, lock screen, settings, IPC
lib/ material.mjs (Material You), vendored material-color-utilities, color worker
wallpaper/ per-output Wallpaper windows, ColorExtractor
settings/ SettingsWindow and its pages
extras/sddm/yuki/ SDDM login theme in the lock screen's design
extras/plymouth/ Plymouth boot theme + image generator
config/ Settings (persisted), Theme (palette from wallpaper/Catppuccin), Wallpapers, Icons
services/ singletons: Niri (IPC event stream), Audio, Media, Net, Notifs, Apps, Lock, Power, Ui
components/ Surface (the fused panel shape), BarPopup, DropSheet, BarButton, PanelHeader, SectionLabel, StyledSlider, …
bar/ Bar + one file per widget
popups/ per-widget popups (incl. PowerMenu, TrayMenu), Launcher, NotificationToasts
lock/ LockScreen (session lock + preview windows), LockSurface (the UI)
pam/ PAM config used by the lock screen
- Popups opened from IPC can't take a pointer grab (Wayland only grants that in response to a click). They close when window focus changes instead of on any outside click.
Apache-2.0, see LICENSE. lib/mcu is Google's
material-color-utilities, also Apache-2.0 (its own LICENSE is kept there).