Configuration
pawbar reads ~/.config/pawbar/pawbar.yaml (override with PAWBAR_CONFIG or --config). The file is hot-reloaded: save it and the bar updates in place.
A config has five top-level sections, all optional:
bar: # bar-global settings
theme: # variables and bar-wide default styling
left: # modules anchored left
middle: # modules centered
right: # modules anchored rightEvery problem in the config is reported with its file position and a "did you mean" hint. By default a broken module entry renders as an error chip (⚠name) while the rest of the bar runs; set bar.strict: true (or pass --strict) to refuse to start instead. pawbar --check validates the config and exits; pawbar --resolved prints the fully merged per-slot configuration for cascade debugging.
bar
bar:
truncate_priority: [right, left, middle]
enable_ellipsis: true
ellipsis: "…"
strict: false
defaults: truetruncate_priority: which anchors keep their content when the bar overflows; earlier wins. Must list all three.enable_ellipsis/ellipsis: mark truncation points.strict: any config issue aborts startup (and rejects hot reloads).defaults: setfalseto drop every module's shipped defaults bar-wide.
theme
theme:
vars:
accent: "#7aa2f7"
warn: orange
defaults:
fg: "@accent"
bold: false
states:
hover: { bold: true } # applies to every module's hovervars: named values. Reference them anywhere with@name. Built-in color names (@urgent,@good,@color112, ...) still work where your vars don't shadow them.defaults: a block applied to every module, plus per-state blocks understates:.
Modules
Each side is a list. An entry is a bare name or a name: {options} mapping:
left:
- ws
- sep
middle:
- clock:
format: "{time:%H:%M}"
right:
- volumesep and space are static separator modules.
Shipped defaults
A module's default configuration is not baked into code: every module ships a small yaml file, in exactly this config syntax, that forms the bottom layer of its cascade. A bare - ws entry gets exactly the contents of that file; nothing more. Empty config means an empty bar, and everything the bar does is written down somewhere you can read:
pawbar defaults # list modules
pawbar defaults ws # print ws's shipped defaults verbatimYou control the whole layer:
- ws:
on:
left: ~ # unbind a shipped binding: null removes it
states:
active: ~ # drop a shipped state's styling entirely
- clock: { defaults: false } # start this entry from a blank slateWith defaults: false (per entry, or bar-wide under bar:) nothing is inherited and the entry becomes fully manual: a module that renders placeholders requires an explicit format, and every option the module declares (tick, warn_at, ...) must be set. Anything missing is a config error listing exactly which keys are absent.
Blocks (style + format)
A block is the styling surface every module shares. All keys are optional; unset keys inherit from the layer below.
| key | meaning |
|---|---|
fg, bg | colors: CSS names, #hex, rgb(r,g,b), @var |
bold, dim, italic, underline, blink, reverse, strikethrough | booleans |
cursor | pointer shape while hovering (CSS cursor names) |
format | placeholder format string (see below) |
template | opt-in Go text/template alternative to format |
Block keys go directly at the top level of a module entry:
- clock:
fg: "@accent"
format: "{time:%H:%M}"Format strings
format uses placeholders: {name} inserts a value the module provides, {name:spec} formats it. Unknown placeholder names are config errors, so typos are caught at load time.
- Time values take a strftime layout:
{time:%A %d %B}. - Numbers take a printf spec without the
%:{vol:3}pads to 3,{load:.2f}renders two decimals. {{and}}are literal braces.
Each module's placeholders are listed in the module reference.
Power users can set template instead: a Go text/template over the same values ({{.vol}}), with round and strftime helper functions. format and template are mutually exclusive per block.
States
Modules expose named condition states they turn on and off themselves (muted, charging, high, ...). A state carries a block that overrides the base styling while active:
- volume:
format: "{icon} {vol}%"
states:
muted: { fg: "@warn", format: "{icon} --" }States can also override module options, not just styling. A state that sets tick: 1m on the clock changes the refresh rate while active.
You can also invent user states and toggle them from mouse bindings; that's how "click to switch format" works:
- clock:
format: "{time:%H:%M}"
states:
full: { format: "{time:%A %d %B %H:%M:%S}" }
on:
left: { cycle: [full] }Two built-in states always exist: hover (pointer over the module) and pressed (button held).
Merge priority
Low to high; later layers override earlier ones per key:
- the module's shipped defaults file
theme.defaults- the entry's top-level block keys
- active states, in order: condition states (module declaration order), user states,
hover,pressed. Each state's block is itself merged from the shipped defaults'states.<s>, thentheme.defaults.states.<s>, then the entry'sstates.<s>.
on: mouse bindings
on:
left: toggle-mute # a module verb (shorthand)
right: { run: "pavucontrol" }
middle: { notify: "hello" }
scroll-up: volume-up
hover: { set: expanded } # state held while hoveringButtons: left, right, middle, scroll-up, scroll-down, scroll-left, scroll-right, plus hover. A binding is one action or a list of actions:
verb: invoke a named action the module implements (bare strings are verb shorthand). Unknown verb names are config errors.run: spawn a command (string or argv list).notify: send a desktop notification.set: toggle a user state.cycle: cycle through user states: none, first, ..., last, none.
Shipped defaults may carry bindings (volume's scroll adjusts volume, clock's right-click opens the calendar); they are plain on: entries in the module's defaults file, visible via pawbar defaults <name>. An on: key for the same button replaces the shipped one, and binding a button to ~ (null) removes it.
Hot reload
Editing the config applies it live:
- unchanged entries keep running untouched,
- a changed entry is reconfigured in place when the module supports it, restarted otherwise,
- theme changes restyle everything without restarts,
- a file that fails to parse is ignored (last good config stays) and the error is logged.
Reordering entries restarts the moved modules; diffing is positional.