A bar that isn't there, until you ask for it.
Your windows get the whole screen. Press Super + / and a bar slides down from the top,
your windows step down to make room, and every panel is a single key away.
Press it again and the screen is yours.
Install · Keys · Configure · Themes · Custom modules · How it works · Hacking
Most bars rent a strip of your screen forever to show you the time. flowbar only shows up when you want something, and it's built to be driven without the mouse.
- Out of the way. Hidden, it takes no pixels and polls nothing.
- One key, then one letter. Super+/ to summon, a letter for each panel, Esc to back out.
- Moves like niri. The bar slides down on niri's own spring and back up the same way when it leaves, and your windows step down to make room.
- One file. Theme, layout, keys, icons and commands all live in one
config.ini, and saving it restyles the open bar on the spot. - Mouse welcome. Every chip, row and toggle is clickable too, and clicking outside closes it.
- Made to be riced. Six themes, every color overridable, CSS variables for your own stylesheet, and any shell command can become a module.
flowbar targets Arch Linux with niri. Any Wayland compositor with layer-shell should work, but niri is where it's at home.
sudo pacman -S --needed vala gtk4 gtk4-layer-shell json-glib wayland wayland-protocols
git clone https://github.com/noturbob/Flowbar && cd Flowbar
make installThat puts flowbar in ~/.local/bin and an annotated config.ini in ~/.config/flowbar/ (it never overwrites one you already have).
Then add two lines to ~/.config/niri/config.kdl:
spawn-at-startup "flowbar" "--daemon"
binds {
Mod+Slash hotkey-overlay-title="Toggle flowbar" { spawn "flowbar"; }
}--daemon starts flowbar hidden at login and warms up its renderer, so the very first summon is instant. Every later flowbar just tells the running one to show or hide.
flowbar --peek <module> flashes just that module in a small pill at the top, with its level, then lets it fade. Hold a key and it stays up. Chain it after the command your media keys already run:
XF86AudioRaiseVolume allow-when-locked=true { spawn-sh "wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.1+ && flowbar --peek volume"; }
XF86AudioLowerVolume allow-when-locked=true { spawn-sh "wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.1- && flowbar --peek volume"; }
XF86AudioMute allow-when-locked=true { spawn-sh "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle && flowbar --peek volume"; }
XF86MonBrightnessUp allow-when-locked=true { spawn-sh "brightnessctl --class=backlight set +10% && flowbar --peek display"; }
XF86MonBrightnessDown allow-when-locked=true { spawn-sh "brightnessctl --class=backlight set 10%- && flowbar --peek display"; }Any module can be peeked. If the bar is already open, the chip itself updates instead.
flowbar --open <module> summons the bar with that panel already open. It makes the launcher a drop-in for your app launcher bind:
Mod+D hotkey-overlay-title="Launch an app" { spawn "flowbar" "--open" "launcher"; }Tools flowbar talks to
| Module | Uses |
|---|---|
| workspaces | niri msg (niri's IPC) |
| wifi | nmcli (NetworkManager) |
| bluetooth | bluetoothctl (bluez-utils) |
| volume | wpctl, pactl (PipeWire) |
| display | brightnessctl |
| media | playerctl |
| clipboard | wl-paste, wl-copy (wl-clipboard) |
| screenshot | niri's built-in screenshot actions |
| notifications | makoctl (mako) |
| night | wlsunset (optional), makoctl |
| updates | checkupdates (pacman-contrib), yay or paru for the AUR |
| power | swaylock, systemctl, niri msg |
Commands that need a terminal (Wi‑Fi passwords, upgrades) open in whatever [commands] terminal says, kitty -e by default.
Summon with Super+/. Every chip on the bar shows its key: press it to drop that module's panel down right under the chip. Esc closes the panel (so does the same key again, in most panels), and Esc once more sends the whole bar away. Arrow keys work anywhere hjkl do.
| Key | Module | Inside the panel |
|---|---|---|
| left | ||
| o | workspaces | every window by workspace · j/k + Enter focus · 1–9 go to workspace |
| t | time | clock, date, week, uptime |
| c | calendar | h/l day · j/k week · H/L month · g today |
| m | media | space play/pause · h/l previous/next |
| center | ||
| ␣ | launcher | type to search apps · ↑/↓ pick · Enter launch |
| y | clipboard | j/k pick · Enter copy · x delete · X clear |
| g | screenshot | a area · s screen · w window · o open folder |
| a | notifications | cards with icon, title and body: on screen, then history (dimmed) · Enter act · x remove (history too) · X clear all · r bring back last · f do not disturb |
| n | night | n night light · f do not disturb |
| u | updates | Enter upgrade · r check now |
| right | ||
| w | wifi | j/k pick · Enter connect or disconnect · space radio · r rescan |
| b | bluetooth | j/k pick · Enter connect or disconnect · space power |
| v | volume | h/l −/+ · m mute · j/k + Enter default device · i inputs/outputs |
| d | display | h/l brightness −/+ |
| s | system | CPU, memory, temperature, battery |
| p | power | l lock · s suspend · e log out · r reboot · o power off, each pressed twice |
Everything also works by pointer. Click a chip to open its panel (click it again to close), click any row to act on it, and click a toggle (Wi‑Fi, Bluetooth, mute, night light, do not disturb, play/pause) to flip it. Click along the volume or brightness meter to jump to that level, or scroll on their chips. Scroll on the workspaces chip to move between workspaces. Clicking anywhere outside the bar closes it. Power actions still take two clicks. Set [bar] mouse = false for keyboard only.
An open panel gets first pick of the keyboard, so letters can mean something local there (s is suspend inside power, not system). The launcher takes that furthest: while it's open, everything you type goes into the search.
Everything lives in ~/.config/flowbar/config.ini. flowbar watches it: save, and the bar rebuilds itself in place, even while it's open. Every setting has a default, so any line (or the whole file) is optional. The file make install gives you documents every option; the highlights:
[theme]
preset = azure # azure · catppuccin · tokyo-night · gruvbox · nord · rose-pine
accent = #00A2E8 # override any single color of the preset
font = CommitMono Nerd Font
radius = 22
[bar]
left = workspaces time calendar media
center = launcher clipboard screenshot notifications night updates
right = wifi bluetooth volume display system power
margin = auto # niri's layout `gaps`: the bar sits as far from the edges as your windows do
mouse = true # clicks and scrolling; false for keyboard only
height = 24 # the size of a stock waybar
[motion]
speed = auto # scales every animation; auto follows niri's `slowdown`, 0 turns it off
[keys]
power = q # rebind any module: a letter, or a key name like F1
[icons]
wifi = # swap any chip's icon
[commands]
terminal = foot # anything that runs a command: kitty -e · alacritty -e · wezterm start --
update = paru
lock = hyprlockModule settings sit in their own sections: [volume] step, [display] step, [night] temperature, [clipboard] keep, [updates] interval, [screenshot] dir.
Mistakes don't fail silently. Run
make run(orflowbarfrom a terminal) and bad config or CSS is reported with the file and line.
Six presets ship in the box. Each is one line: preset = <name>.
| azure · the default | ![]() |
| catppuccin | ![]() |
| tokyo-night | ![]() |
| gruvbox | ![]() |
| nord | ![]() |
| rose-pine | ![]() |
Any preset color can be overridden in [theme]: accent, accent-2 (the active chip, today in the calendar), background, foreground, good ("on" states). Plus opacity, font, font-size and radius. On compositors with the ext-background-effect protocol (niri 26.04+), what shows through the bar and its panels is blurred.
For anything past colors, drop a style.css next to your config. It's GTK CSS, layered over flowbar's own, reloaded on save, and it can use the same variables the built-in theme uses:
/* square, flat, loud */
.bar, .card { border-radius: 0; box-shadow: none; }
.chip.active { background: var(--accent); }
.chip.active .icon, .chip.active label { color: var(--background); }| Variables | |
|---|---|
| colors | --accent --accent-2 --background --foreground --good |
| shape | --opacity --radius --bar-height --gap --font --font-size |
| motion | --t-drop --t-fade --t-chip --t-leave --t-quick (already scaled by [motion] speed) |
| Classes | |
|---|---|
.flow |
the root; .flow.hidden while it's away |
.peek · .osd |
the --peek window and its pill |
.bar · .chip · .chip.active |
the bar and its chips |
.key · .icon |
a chip's key badge and icon |
.card |
the panel under a chip |
.row · .row.cursor |
list rows and the selected one |
.big · .sub · .dim · .status · .accent |
panel text |
Any shell command can become a module, no code needed. Define it, then list its name in a [bar] line:
[bar]
right = weather wifi bluetooth volume display system power
[custom.weather]
key = e
icon =
title = Weather
exec = curl -s 'wttr.in/?format=%c+%t'
interval = 900 # seconds between runs while the bar is up
on-enter = xdg-open https://wttr.in
[custom.vpn]
key = x
icon =
exec = nmcli -t -f TYPE connection show --active | grep -q vpn && echo on || echo off
on-enter = nmcli connection up id my-vpn
close-on-enter = false # keep the bar open after EnterThe chip shows the first line of the command's output and the panel shows all of it. Enter runs on-enter.
A few details, for the curious:
- A layer-shell overlay. flowbar is a GTK 4 window on the overlay layer, anchored to the top and both sides. While it's up it takes the keyboard, so a single letter can mean something.
- Windows that make room. When the bar appears it reserves a strip at the top edge, and niri moves your windows out of it. The strip is claimed in one step the moment you press the key, not grown with the bar: every change to it resizes every window, and apps re-flow their text a beat after the last resize, so a strip that grew frame by frame left terminals visibly settling after the bar had landed. In one step they finish re-flowing while the bar is still sliding in on niri's default spring.
- Your spacing, not ours. The gap around the bar comes from niri's own
gaps, so it lines up exactly with your window edges. - One instance. flowbar is a single-instance GTK application: running
flowbaragain just toggles the one that's already there, which is why a bind can be a plainspawn "flowbar". - Text that fits. A chip is at most fourteen characters wide. A longer window title, song or clipboard entry scrolls to show its end and back, and only while the bar is on screen.
- Quiet when hidden. Modules only poll while the bar is on screen, and slow checks (package updates) are cached.
- Clipboard without a daemon. History comes from
wl-paste --watchand stays in memory. Copies a password manager marks as sensitive are never recorded.
flowbar is written in Vala, which compiles to C against GTK 4: a small native binary, GObject underneath, and GTK's CSS engine doing the animation.
make run # build, stop any running flowbar, run in the foreground with warnings visible| File | What's in it |
|---|---|
src/flowbar.vala |
the app: window, summon and dismiss, keys, panel placement, the Module base class and helpers |
src/config.vala |
config.ini reading, theme presets, the CSS variables |
src/style.vala |
the stylesheet |
src/custom.vala |
[custom.NAME] modules |
src/<module>.vala |
one file per built-in module |
config.ini |
the annotated default config |
A module is a chip plus a panel. Here's a complete one:
using Gtk;
class Uptime : Module {
Label since = label ("", "sub");
public Uptime () {
base ("z", ""); // its key and icon
every = 30; // refresh every 30 seconds while the bar is up
panel.append (label ("Uptime", "status"));
panel.append (since);
}
public override async void refresh () {
value.label = yield sh ("uptime -p"); // the chip's text
since.label = yield sh ("uptime -s");
}
public override bool on_key (string k) { // keys while its panel is open
if (k != "Return") return false;
in_terminal ("btop");
return true;
}
}Register it by name in make_module () in src/flowbar.vala, and it can be placed from [bar] like any other. Helpers to build with: sh () runs a command and returns its output, launch () fires one off, in_terminal () runs one in the user's terminal, act () runs a command and then refreshes, dismiss () hides the bar, Picker is a keyboard-driven list, key_row () makes a key-menu row, and Config.str / num / flag () read settings.
- Nothing happens on Super+/. Check that
~/.local/binis on niri'sPATH, or bind the full path. - Night light does nothing. It needs
wlsunset(sudo pacman -S wlsunset). The panel tells you when it's missing. - Do not disturb does nothing. mako needs the mode defined. Add this to
~/.config/mako/config:[mode=do-not-disturb] invisible=1
- A theme change didn't apply. Run
make runand look for a line number: flowbar reports config and CSS mistakes instead of skipping them.





