Hyprland Waybar configuration and themes

On this page

Waybar can be a primary bar or an independent backup during shell maintenance. It reads JSON/JSONC and GTK CSS without requiring a complete Quickshell shell.

Start without private helper scripts. Choose a test edge that does not conflict with existing surfaces, and stop other bars on that edge before a permanent switch.

1. Create an independent test configuration

Create ~/.config/waybar-lab/ and save this complete config.jsonc:

{
  "layer": "top",
  "position": "bottom",
  "height": 32,
  "spacing": 8,
  "modules-left": ["hyprland/workspaces"],
  "modules-center": ["clock"],
  "modules-right": ["cpu", "memory"],
  "hyprland/workspaces": {
    "format": "{name}",
    "on-click": "activate"
  },
  "clock": {
    "format": "{:%H:%M}",
    "tooltip-format": "{:%Y-%m-%d %A}"
  },
  "cpu": { "format": "CPU {usage}%" },
  "memory": { "format": "RAM {percentage}%" }
}

Text labels exclude icon-font issues during initial testing. The bottom edge is an example and can still conflict with existing UI.

Save style.css alongside it:

* {
  font-family: sans-serif;
  font-size: 13px;
  border: none;
  border-radius: 0;
  min-height: 0;
}

window#waybar {
  background: #1e2530;
  color: #f1f4f8;
}

#workspaces button {
  padding: 0 8px;
  color: #b8c3d2;
  background: transparent;
}

#workspaces button.active {
  color: #ffffff;
  background: #40516b;
}

#clock, #cpu, #memory {
  padding: 0 8px;
}

Launch inside the Hyprland session:

waybar -c ~/.config/waybar-lab/config.jsonc -s ~/.config/waybar-lab/style.css -l info

Expect workspaces, clock, CPU and memory. Keep it in the foreground for logs; Ctrl+C stops the test without changing autostart. Waybar configuration

2. Split files after the baseline works

waybar/
├── config.jsonc
├── modules/
│   ├── workspaces.jsonc
│   ├── audio.jsonc
│   └── network.jsonc
├── style.css
├── theme.css
└── styles/
    ├── base.css
    └── modules.css

Keep module order in the main configuration and add this include property:

{
  "include": [
    "~/.config/waybar/modules/workspaces.jsonc",
    "~/.config/waybar/modules/audio.jsonc",
    "~/.config/waybar/modules/network.jsonc"
  ]
}

This is only the include portion, not a complete bar configuration. Each module file contains a complete JSON object. Avoid duplicate option definitions whose merge precedence is unclear. Layout chooses visible modules; module files define their behaviour.

3. Workspaces and multiple outputs

{
  "hyprland/workspaces": {
    "format": "{name}",
    "on-click": "activate",
    "persistent-workspaces": { "*": 5 }
  }
}

Persistent buttons do not mean each workspace already contains a window. Verify ownership, visibility and switching across outputs before adding scroll actions. Missing icon glyphs are a font issue, not workspace logic. Workspace module

4. Audio and pointer actions

With PipeWire/WirePlumber and a Waybar build supporting its module:

{
  "wireplumber": {
    "format": "VOL {volume}%",
    "format-muted": "MUTE",
    "on-click": "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle",
    "on-scroll-up": "wpctl set-volume -l 1.0 @DEFAULT_AUDIO_SINK@ 5%+",
    "on-scroll-down": "wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"
  }
}

Add wireplumber to modules-right. The upper-volume command caps output at 1.0. Test wpctl independently before clicks and scrolling to separate audio-system failure from event handling.

An existing setup may instead use the PulseAudio module through PipeWire's compatibility service. Choose one matching the build and environment. For input/output drawers, verify each module independently before group layout and animation. WirePlumber module

5. Network and external panels

{
  "network": {
    "interval": 10,
    "format-ethernet": "LAN {ipaddr}",
    "format-wifi": "Wi-Fi {signalStrength}%",
    "format-disconnected": "Offline",
    "tooltip-format-wifi": "{essid}\n{ipaddr}/{cidr}"
  }
}

Add network to the layout. Before adding a click action for a TUI or GUI, ensure that program is installed. The network manager, panel and bar are separate components.

Shared panel-toggle scripts should define window class, repeated-click behaviour and error output. A live process does not necessarily represent a visible window, so process-name matching alone is not sufficient.

6. GTK CSS and semantic colours

GTK CSS is not browser CSS. Diagnose parse errors before adding layout or animation syntax copied from websites.

Keep imports before other rules:

@import "theme.css";
@import "styles/base.css";
@import "styles/modules.css";

Define roles in theme.css:

@define-color main-bg #1e2530;
@define-color main-fg #f1f4f8;
@define-color accent #9bbdf0;

Module styles use @main-bg, @main-fg and @accent. Check warning, disabled, selected and hover states separately for readability.

7. Optional Matugen generation

Keep static colours until modules and actions work, then generate the same roles through Matugen.

Check whether .hex includes # using temporary output; the tested 4.1.0 version does. Validate CSS and reload a specific instance. A changed template file does not prove a running bar refreshed.

8. User services and fallback

After foreground verification, inspect existing units before adding another:

systemctl --user cat waybar.service
systemctl --user status waybar.service

If absent, write a unit for the actual session target as explained in session diagnosis. Check ExecStart paths and session-end behaviour.

To test an existing service temporarily:

systemctl --user start waybar.service

Verify UI, logs and division of responsibility before enabling it persistently. Returning to backup status requires stopping the service and removing its actual autostart source; disabling a unit does not remove a separate Hyprland startup command.

9. Troubleshooting order

  1. Use trace logging for JSONC, module and CSS errors.
  2. Confirm explicit config/style paths and includes.
  3. Remove new modules and retest the minimal bar.
  4. Run click commands independently and inspect PATH.
  5. Separate font/icon/style errors from data-module failures.
  6. If foreground works but a service fails, inspect its environment and logs.

The final structure should retain functional modules without decoration and leave the primary desktop usable when the backup bar stops.