Setting up Waybar

Demystifying Waybar styling and layout configuration on CachyOS, featuring modular styles and CSS color injection.

The Customization Paradox

You finally booted into CachyOS. Magnificent, right? But then you look up, and the default status bar looks completely out of place, or worse — nonexistent.

Configuring your desktop status bar is arguably the easiest first step into ricing Linux. So let’s get to it.

OK, I lied. Maybe you should start with configuring your Hyprland configurations - but that shifted to Lua recently and I have not gotten to migrating mine yet.

Below are some of the tools I used, but the text editor can really be anything you want. I may have used Visual Studio Code here and there - but as long as you can edit text, you’re fine.

Ingredients
RequirementDetailsReference
WaybarHighly customizable Wayland status barhttps://github.com/Alexays/Waybar
Nerd FontsSystem icons (Symbols Nerd Font preferred)https://www.nerdfonts.com/
CachyOS / ArchAn active Wayland composited desktop environmenthttps://cachyos.org/
Text EditorFor dealing with JSONC and CSS quirkshttps://www.nano-editor.org/

You can find my rice here: https://github.com/vnsnippets/arch-basmati-rice.


The Blueprint (config.jsonc)

Waybar relies on a master JSONC (JSON with Comments) structure. Think of this as defining the overall layout of your status bar and what it will hold.

Our setup splits the top bar into three functional target zones: Left (Time), Center (Workspaces), and Right (System States & Hardware toggles).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
{
    "position": "top",
    "margin-top": 10,
    "margin-left": 10,
    "margin-right": 10,
    "spacing": 4,
    "modules-left": ["clock"],
    "modules-center": ["hyprland/workspaces"],
    "modules-right": [
        "network",
        "pulseaudio",
        "battery",
        "power-profiles-daemon",
        "custom/power"
    ],

    // Each of the modules are then further configured
    // below with their own parameters
}

There is a whole world of modules supported listed in the official wiki at https://github.com/alexays/waybar/wiki.

Modules: Clock

Complete wiki at https://github.com/Alexays/Waybar/wiki/module:-clock.

1
2
3
4
"clock": {
    "format": "{:%Y-%m-%d %H:%M}",
    "tooltip": false
}

I don’t need much from my clock, so moving on.

Modules: Workspaces

Complete wiki at https://github.com/Alexays/Waybar/wiki/Module:-Hyprland#workspaces.

1
2
3
4
5
6
7
8
9
"hyprland/workspaces": {
    "format": "",
    "active-only": false,
    "all-outputs": false,
    "sort-by-number": true,
    "format-icons": {
        "default": ""
    }
}

Now, I don’t know what fonts you have installed and if this will show correctly on your screen - but for the format, I am using a big filled circle from (any) Nerd Fonts.

This gives it a “dot” appearance, but the colors will tell me which workspace is active or not.

Setting "all-outputs": false ensures your workspaces don’t leak across secondary displays. If you multi-task heavily on twin monitors, leaving this true will cause visual confusion as workspace IDs start stepping on each other’s toes.

Next we will dive into the modules, followed then by the styles which brings in the nice colors and aesthetics.

Modules: Network

Complete wiki at https://github.com/Alexays/Waybar/wiki/Module:-Network.

1
2
3
4
5
6
7
"network": {
    "format-wifi": "<span size='13000' foreground='#f5e0dc'>  </span>{essid}",
    "format-ethernet": "<span size='13000' foreground='#f5e0dc'>󰤭  </span> Disconnected",
    "format-linked": "{ifname} (No IP) ",
    "format-disconnected": "<span size='13000' foreground='#f5e0dc'>  </span>Disconnected",
    "tooltip-format-wifi": "Signal Strength: {signalStrength}%"
},

Notice how we leverage Pango markup syntax directly inside the JSON structure to inject explicit font sizes and hex codes directly to the icons.

Modules: Pulse Audio

Complete wiki at https://github.com/Alexays/Waybar/wiki/Module:-PulseAudio.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
"pulseaudio": {
    "format": "{icon}",
    "format-muted": "",
    "tooltip-format": "({format_source}) {desc}",
    "format-source": "Active",
    "format-source-muted": "Muted",
    "on-click": "pactl set-sink-mute @DEFAULT_SINK@ toggle",
    "format-icons": {
        "headphone": "",
        "default": ["", "", ""]
    }
}

Setting format determines the structure of what will show on the status bar component, and the specific formats like format-muted or format-icons determines the actual content.

In particular, format-icons.default takes an array to adapt the icon based on your volume level.

Battery

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
"battery": {
    "states": {
        "warning": 30,
        "critical": 15
    }, 
    "format": "{icon} {capacity}%",
    "format-charging": " {capacity}%",
    "format-plugged": " {capacity}%",
    "format-alt": "{icon} {capacity}%", 
    "format-icons": ["", "", "", "", ""]
}

The formatting here is similar to Pulse Audio, but battery also takes a states.warning and states.critical setting to allow style changes based on whether your battery is below those levels.

Modules: Power Profiles

Complete wiki at https://github.com/Alexays/Waybar/wiki/Module:-PowerProfilesDaemon.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
"power-profiles-daemon": {
    "format": "{icon}",
    "tooltip": false,
    "format-icons": {
        "default": "",
        "performance": "",
        "balanced": "",
        "power-saver": ""
    }
}

This module allows changing the battery power mode, and the icon can be set differently for each mode as well.

Modules: Custom (Power)

Complete wiki at https://github.com/Alexays/Waybar/wiki/Module:-Custom.

1
2
3
4
5
"custom/power": {
    "format": "⏻",
    "tooltip": false,
    "on-click": "poweroff"
}

This is a bit of a special one.

You can create custom components that invokes scripts. In my case, I added a shut-down button that simply runs the poweroff command when clicked.

Styles

If you dump all your styling logic and hex color codes into a massive, monolithic file, modifying your theme later will feel like walking through mud. Instead, we can implement modular stylesheet grouping.

We’ll abstract colors out completely into a palette declaration module called colors.css, and import it into our layout style definitions.

Defining the Core Palette (colors.css)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@define-color module-background #232634;
@define-color module-border #394250;

@define-color workspace-background #232634;
@define-color workspace-active #232634;

@define-color teal #81c8be;
@define-color green #a6d189;
@define-color red #e78284;
@define-color yellow #e5c890;

Assembling the style (style.css)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@import "colors.css"; 

* {
    font-family: "Noto Sans", "Noto Sans Mono", "Symbols Nerd Font";
    font-size: 14px;
    min-height: 0;
    transition: all 0.3s cubic-bezier(.55, .055, .675, .19);
}

window {
    background: transparent;
}

#workspaces button {
    min-width: 12px;
    min-height: 12px;
    padding: 0 6px;
    margin: 6px 2px;
    background-color: @workspace-background;
    color: transparent;
    border: none;
    border-radius: 100%;
}

#workspaces button.active {
    background-color: @workspace-active;
    min-width: 24px;
    border-radius: 24px;
}

#bluetooth,
#pulseaudio,
#clock,
#battery,
#power-profiles-daemon,
#network,
#custom-power {
    padding: 8px 12px;
    border: 1px solid @module-border;
    border-radius: 12px;
    background-color: @module-background;
}

This is simply CSS - which means you can add :hover and other CSS selectors to further customize everything.

Similarly, by binding attributes onto generic CSS variable placeholders (@module-background and @module-border), changing the theme across the whole status bar down the road requires nothing more than editing color palette in colors.css.

Custom Layout Options: Chaining vs Islands

When building bars, ricing enthusiasts often argue over structural layout schemes. Let’s do a quick comparison of the two dominant structural behaviors:

Layout StyleThe GoodThe Bad
Monolithic Unified BlockClean traditional look, zero gap calculation issues, easy alignment.Harder to accent specific metric warnings visually.
Pill / Island ConfigurationExceptional modularity, beautiful modern gaps, distinct element groups.Margin configurations can get tedious quickly.

Our configuration leverages a clean standalone island configuration where each distinct functional module gets isolated padding rules, structural border treatments, and specific border radius to create a floating pill aesthetic over a transparent compositor layer.

Complete Style

Here is my full stylesheet with all the hovers and specific colors.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
@import "colors.css"; 

* {
    font-family: "Noto Sans", "Noto Sans Mono", "Symbols Nerd Font";
    font-size: 14px;
    min-height: 0;
    transition: all 0.3s cubic-bezier(.55, .055, .675, .19);
}

window {
    background: transparent;
}

#workspaces {
    background-color: transparent;
}

#workspaces button {
    min-width: 12px;
    min-height: 12px;
    
    padding: 0 6px;
    margin: 6px 2px;
    
    background-color: @workspace-background;
    background-size: 10px 10px; 
    background-repeat: no-repeat;
    background-position: center;
    color: transparent;
    
    border: none;
    border-radius: 100%;
    box-shadow: inset 0 0 0 1px transparent;
}

#workspaces button:hover {
    background-color: @workspace-active;
    opacity: 0.7;
    box-shadow: inherit;
    text-shadow: inherit;
}

#workspaces button.active {
    background-color: @workspace-active;
    min-width: 24px;
    border-radius: 24px;
}

#bluetooth,
#pulseaudio,
#clock,
#battery,
#power-profiles-daemon,
#cpu,
#memory,
#disk,
#temperature,
#backlight,
#wireplumber,
#tray,
#network,
#mode,
#scratchpad,
#custom-power {
    padding: 8px 12px;
    border: 1px solid @module-border;
    border-radius: 12px;
    background-color: @module-background;
}

#clock {
    color: @text;
}

#pulseaudio,
#wireplumber {
    color: @teal;
}

#network {
    color: @yellow;
}

#idle_inhibitor {
    color: #7cb342;
}

#idle_inhibitor.activated {
    color: @red;
}

#battery {
    color: @green;
}

#power-profiles-daemon.performance {
    color: @red;
}

#power-profiles-daemon.balanced {
    color: @teal;
}

#power-profiles-daemon.power-saver {
    color: @green;
}

#custom-power {
    color: @red;
}

#custom-power:hover {
    background-color: @red;
    color: @module-border;
    border-radius: 10px;
}

And that’s it ✌️

You now have a modularized status bar configuration that will survive whatever system alterations you cook up next.

Full disclosure: I stopped using Waybar a week into my journey and went for coding my own desktop aesthetics from scratch.

Till the next one, stay techy :)