Desktop Polish: DankMaterialShell and the Perfect Rofi Launcher


The Integration Problem

A Wayland desktop has many components:

  • Compositor (Niri)
  • Desktop shell (panel, system tray)
  • App launcher
  • Notifications
  • Lock screen

Each is a separate project with its own design language. Making them feel unified requires intentional coordination.

DankMaterialShell: The Shell Layer

DankMaterialShell (DMS) is a Quickshell-based desktop shell. It provides:

  • Top panel with workspaces, systray, clock
  • Power menu
  • App spotlight
  • Clipboard history

Why DMS: It uses Material Design 3 tokens, which provides a coherent design language. It also works on both Hyprland and Niri—compositor agnostic.

IPC: The Control Pattern

DMS exposes functionality via IPC. This is the same pattern as Kitty’s remote control: the shell is a server, scripts are clients.

dms ipc call powermenu toggle    # Power menu
dms ipc call spotlight toggle    # App launcher
dms ipc call clipboard toggle    # Clipboard history
dms ipc call settings toggle     # Settings panel

The benefit: Compositor keybindings can trigger shell features without coupling.

// In niri config
Mod+X { spawn-sh "dms ipc call powermenu toggle"; }

The compositor doesn’t know about DMS internals. It just calls an IPC command. Tomorrow I could replace DMS with something else, and the keybinding still works if it exposes the same IPC.

Rofi: The Launcher Layer

DMS has a built-in launcher (spotlight), but I prefer Rofi for its flexibility and speed.

The challenge: Making Rofi visually match DMS.

Design Goals

  1. Fullscreen grid — Like macOS Launchpad
  2. Matching colors — Same palette as DMS (Catppuccin Mocha)
  3. Selection highlight — Use Niri’s focus ring color for consistency

Color Coordination

I extracted colors from DMS’s theme and Niri’s focus ring:

ElementColorSource
Background#1a1a1a @ 90%Neutral (not pure black)
Text#cdd6f4Catppuccin text
Subtext#a6adc8Catppuccin subtext0
Selection#7fc8ffNiri focus-ring.active-color
Border#45475aCatppuccin surface1

The insight: Using Niri’s focus ring color for selection means the launcher’s selection matches window focus everywhere. Visual consistency without thinking.

Rofi theme: ~/.config/rofi/themes/launchpad.rasi
* {
    font: "Hack Nerd Font 10";

    bg0: #1a1a1ae6;
    bg1: #313244;
    bg2: #45475a80;
    bg3: #45475a;
    bg4: #7fc8ffe6;  /* Niri focus-ring color */

    fg0: #cdd6f4;
    fg1: #a6adc8;

    background-color: transparent;
    text-color: @fg0;
}

window {
    fullscreen: true;
    padding: 1em;
    background-color: @bg0;
}

inputbar {
    background-color: @bg2;
    margin: 0px calc( 50% - 160px );
    padding: 8px 12px;
    border: 2px;
    border-radius: 12px;
    border-color: @bg3;
    children: [icon-search, entry];
}

icon-search {
    expand: false;
    filename: "search";
}

entry {
    placeholder: "Search apps...";
    placeholder-color: @fg1;
}

listview {
    margin: 48px calc( 50% - 560px );
    spacing: 32px;
    columns: 6;
    fixed-columns: true;
}

element {
    padding: 12px;
    orientation: vertical;
    border-radius: 12px;
}

element selected {
    background-color: @bg4;
}

element-icon {
    size: 64px;
    horizontal-align: 0.5;
}

element-text {
    horizontal-align: 0.5;
}

Arrow Key Navigation

By default, Rofi uses arrow keys for text cursor movement. For a grid, we want them to navigate the grid:

rofi -show drun -show-icons \
  -kb-move-char-back "" \
  -kb-move-char-forward "" \
  -kb-row-left "Left" \
  -kb-row-right "Right" \
  -theme ~/.config/rofi/themes/launchpad.rasi

The tradeoff: Text cursor movement moves to Ctrl+B/F. Acceptable for a launcher where you rarely need cursor positioning.

Niri Integration

Bind Rofi in Niri with the full options:

Mod+R hotkey-overlay-title="App Launcher" {
    spawn "rofi" "-show" "drun" "-show-icons"
          "-kb-move-char-back" ""
          "-kb-move-char-forward" ""
          "-kb-row-left" "Left"
          "-kb-row-right" "Right"
          "-theme" "~/.config/rofi/themes/launchpad.rasi";
}

The Consistency Pattern

Looking across all components:

ComponentSelection ColorSource
Niri focus ring#7fc8ffNative
Rofi selection#7fc8ffMatched to Niri
DMS highlightsMaterial BlueNative (close enough)
ComponentIPC PatternCommand
DMSdms ipc call XCompositor-agnostic
Kittykitty @ XShell-agnostic
Niriniri msg XDirect compositor control

The pattern: each component exposes its own IPC. Keybindings call IPC. Components are decoupled.

Other Shell Components

Notifications (SwayNC)

Mod+N { spawn-sh "swaync-client -t -sw"; }

Toggle notification center. SwayNC follows similar IPC patterns.

Lock Screen (Swaylock)

Super+Alt+L { spawn "swaylock"; }

Simple spawn. Swaylock handles everything internally.

Screenshots

Niri has built-in screenshot commands. For area selection, I use grim + slurp:

Print { screenshot; }
Shift+Print { spawn-sh "grim -g \"$(slurp)\" ~/Pictures/Screenshots/$(date +%Y%m%d_%H%M%S).png"; }

Design Decisions

DecisionTradeoffWhy
Rofi over DMS spotlightExtra configMore flexible, faster
Niri focus color in RofiManual syncVisual consistency worth the effort
IPC for shell featuresExtra call overheadDecouples compositor from shell
Fullscreen launcherDramatic but coveringMatches macOS feel I like

Dependencies

The full stack requires:

# Shell
quickshell dankmaterialshell

# Launcher
rofi

# Notifications
swaync

# Lock
swaylock

# Screenshots
grim slurp wl-clipboard

# Fonts
ttf-hack-nerd

Testing Changes

Preview Rofi theme changes:

rofi -show drun -show-icons -theme ~/.config/rofi/themes/launchpad.rasi

Restart DMS after settings changes:

dms restart

Niri hot-reloads automatically.


Next in this series: My 2026 Linux Desktop: Niri + DankMaterialShell + Kitty