Skip to content

Bar

Bars are defined as named subtables under [bar.*]. Each bar is spawned on every connected monitor, then per-monitor overrides are applied.

[bar]
order = ["main"] # layer-shell creation order
[bar.main]
position = "top" # top | bottom | left | right
enabled = true
auto_hide = false # slide out after pointer leaves; reveal from edge trigger strip
show_on_workspace_switch = true # with auto_hide: briefly reveal when the active workspace changes
reserve_space = true # reserve compositor exclusive zone / push windows away
layer = "top" # top | overlay; overlay appears above fullscreen apps
thickness = 34 # bar cross-axis size in pixels (height for horizontal, width for vertical)
background_opacity = 1.0 # 0.0 (transparent) to 1.0 (opaque)
border = "outline" # color role or #RRGGBB for the bar outline
border_width = 0.0 # inside outline width in pixels; 0 disables it
shadow = true # cast the global [shell.shadow]
contact_shadow = false # dark gradient between an attached panel and the bar (depth at the seam)
panel_overlap = 1 # logical px an attached panel overlaps the bar edge to hide the seam
radius = 12 # global corner radius fallback
radius_top_left = 12
radius_top_right = 12
radius_bottom_left = 12
radius_bottom_right = 12
margin_ends = 180 # inset from each end of the bar along its main axis
margin_edge = 10 # distance from the nearest screen edge (positive values float the bar)
margin_opposite_edge = 0 # extra reserved space on the inward side of the bar (below for top, above for bottom)
padding = 14 # main-axis padding from bar edges to start/end widget sections
widget_spacing = 6 # gap between widgets within a section
scale = 1.0 # content scale multiplier for icons and text
font_weight = "regular" # "regular" | "bold" — primary label weight for bar widgets
font_family = "" # typeface for this bar's widgets; empty inherits the global font
# Default capsule style for all widgets on this bar (see Widget Capsule section)
capsule = false
capsule_fill = "surface_variant"
capsule_thickness = 0.76 # capsule size across the bar as a fraction of bar thickness (1.0 fills the bar)
capsule_radius = 8.0 # omit for automatic pill radius
capsule_opacity = 1.0
# capsule_border = "outline" # omit this key for no border by default
start = ["launcher", "wallpaper", "workspaces"]
center = ["clock"]
end = ["media", "tray", "notifications", "clipboard", "network", "bluetooth", "volume", "brightness", "battery", "control-center", "session"]

Radius precedence: radius is the global fallback; per-corner values override it when provided.

A negative per-corner radius carves a concave corner instead of a convex round-off: the corner bulges outward into a rounded spike that hugs the screen edge, so the bar appears to melt into the screen. The spike’s size is the absolute value (e.g. radius_bottom_left = -16 gives a 16 px spike).

Concave spikes only render on the bar’s two inner corners — the corners facing away from the screen edge — because the bar can’t grow along the edge it’s anchored to:

Bar positionInner corners (set these negative)
topradius_bottom_left, radius_bottom_right
bottomradius_top_left, radius_top_right
leftradius_top_right, radius_bottom_right
rightradius_top_left, radius_bottom_left

The spike depth is capped at half the bar thickness. A negative value on an outer (screen-edge) corner has no room to grow and is clipped, so leave those at 0 or a positive radius.

Multiple [bar.*] entries are supported — each is independently configured and rendered on all monitors.

[bar].order controls the order Noctalia creates bar layer-shell surfaces. This matters only for bars with reserve_space = true, and the compositor still owns the final exclusive-zone arrangement. Names omitted from order are appended after the listed names.

The dead zone is the bar margin outside the start/center/end widget sections — typically the inset created by margin_ends. Configure shell commands under [bar.<name>.dead_zone]:

[bar.main.dead_zone]
command = "noctalia msg launcher toggle" # left click
right_command = "" # empty = open Control Center (default)
middle_command = "..." # middle click
scroll_up_command = "..."
scroll_down_command = "..."

Right-click still opens Control Center when right_command is empty. Set right_command to override that on dead-zone clicks only. While an attached panel is open, right-click anywhere on the bar still toggles Control Center unless a dead-zone right_command is configured and the click lands in the dead zone.

Per-monitor overrides can set any dead-zone command under [bar.<name>.monitor.<match>.dead_zone]; unset fields inherit from the bar defaults.


Inside a bar, add named monitor subtables under [bar.<name>.monitor.*]. First match wins, in file order.

[bar.main.monitor.dp1]
match = "DP-1" # connector name or description substring
position = "bottom" # top | bottom | left | right
enabled = true
thickness = 44
background_opacity = 0.9
border_width = 1.0
radius = 0
radius_top_left = 12
radius_top_right = 12
radius_bottom_left = 0
radius_bottom_right = 0
padding = 20
widget_spacing = 6
start = []
center = ["workspaces"]
end = ["volume", "clock"]

match resolution — compared against:

  1. Exact connector name (eDP-1, DP-1, HDMI-A-1, …)
  2. Any substring of the monitor description string ("LG", "4K", "DELL", …)

match defaults to the subtable key name when omitted, so [bar.main.monitor."DP-1"] without a match field works too.

Only the fields you specify are overridden; everything else falls through to the [bar.*] defaults. Supported override fields: all bar fields including position, dead_zone, plus auto_hide, show_on_workspace_switch, reserve_space, layer, scale, background_opacity, color, font_family, and all capsule_* keys.

Shadow blur, offset, and alpha are global under [shell.shadow]. Bars only expose shadow = true|false.

contact_shadow = true adds a dark gradient at the seam between an attached panel and the bar, giving the panel a slight lifted-off-the-bar feel. It is independent of shadow and only takes effect on attached panels; the change applies on the next panel open.

panel_overlap controls how many logical pixels an attached panel is pulled into the bar so the two surfaces share an edge instead of leaving a hairline gap. The ideal value depends on your compositor and the output’s fractional scale (it comes down to physical-pixel rounding), so it is tunable rather than fixed. The default is 1. If you see a faint seam line, try 0; if a gap appears, raise it. Negative values push the panel away from the bar. Because the right value tracks fractional scale, you can set it per monitor with a [bar.<name>.monitor.*] override on a mixed-scale multi-monitor setup. In the Settings GUI it lives under Bar → Layout → Advanced.

capsule_thickness sets the widget capsule size across the bar (its height on a horizontal bar, width on a vertical bar) as a fraction of the bar thickness. The default is 0.76; 1.0 makes capsules fill the full bar thickness and lower values shrink them, leaving more margin on either side. It applies to every capsule on the bar regardless of per-widget content scale. In the Settings GUI it lives under Bar → Capsules.

font_family sets the typeface for this bar’s widget labels. Leave it empty (the default) to inherit the global [shell] font_family. A single widget can override it with [widget.<name>] font_family, and it can be set per monitor with a [bar.<name>.monitor.*] override. The Settings GUI exposes a searchable font picker under Bar → Widgets, with a per-widget picker in each widget’s settings.

border_width draws an inside outline using border. Attached panels stay borderless so they remain visually clean against the bar.


When auto_hide = true, the bar:

  • Slides out once the pointer leaves the bar.
  • Slides back in when the pointer reaches the matching screen edge. With margin_edge > 0, the float gap is included in the layer surface so reveal works from the physical screen edge. With margin_edge = 0, the bar stays flush to the edge; pointer hit-testing still uses the full layer surface so reveal does not immediately cancel.
  • While auto-hide is active, pointer hit-testing uses the full bar layer surface so entering from the edge strip does not immediately leave again.
  • With show_on_workspace_switch = true (default), briefly reveals when the active workspace changes on that monitor, then hides again if the pointer is not over the bar.
  • IPC bar-show, bar-hide, and bar-toggle accept optional [bar-name] [monitor-selector] arguments; omit both to affect every bar instance. They use the same hide/reveal animation instead of tearing down surfaces. With auto_hide, they retract or reveal the bar like pointer auto-hide; edge reveal still works after bar-hide. With auto_hide off, bar-hide hides the bar and always frees the compositor gap until bar-show (or bar-toggle while showing), regardless of reserve_space.

reserve_space controls whether the bar keeps a compositor exclusive zone. With auto_hide = false, reserve_space = true keeps windows pushed away; reserve_space = false overlays the bar on top of full-screen clients. With auto_hide = true, reserve_space = false retracts the bar as an overlay (no gap); reserve_space = true keeps the gap while the bar slides out of view.

margin_opposite_edge adds extra reserved space on the inward side of the bar (below a top bar, above a bottom bar, and so on for vertical bars). The bar itself does not move; the compositor exclusive zone grows so tiled and maximized windows stop further away. Use it when a compositor ignores or under-reserves struts — for example Labwc with a maximized window that still touches the bar. Requires reserve_space = true. The default is 0.

layer = "overlay" shows the bar above fullscreen apps. Attached panels (for example Control Center when it attaches to the bar) follow the bar’s layer. Centered or floating panel placements keep each panel’s own layer (typically overlay).

The IPC bar-layer-set <top|overlay> [bar-name] [monitor-selector] switches the layer of matching bar instance(s) at runtime; omit bar-name to update every bar. The change is transient and resets to the configured layer on the next config reload. This is handy for peeking the bar above a fullscreen window on a keybind, for example in niri:

binds {
Mod+B { spawn-sh "noctalia msg bar-layer-set overlay"; }
Mod+Shift+B { spawn-sh "noctalia msg bar-layer-set top"; }
}

Each widget can have a capsule (pill-shaped background + optional border). Settings cascade from bar defaults down to per-widget overrides.

Set under [bar.<name>] or [bar.<name>.monitor.*]:

SettingTypeDefaultDescription
capsuleboolfalsetrue gives every widget a capsule unless [widget.*] sets capsule = false.
colorstring(unset)Default icon + label color role for every widget on this bar; fixed hex colors are also supported.
icon_colorstring(unset)Overrides color for icons only on this bar; fixed hex colors are also supported. When unset, icons inherit color.
capsule_radiusnumber(auto pill)Default capsule corner radius in logical pixels before scale is applied (clamped 0–80). Also used by workspace pills and taskbar workspace groups. Omit to use automatic pill radius.
capsule_fillstringsurface_variantDefault capsule background color role; fixed hex colors are also supported.
capsule_foregroundstring(unset)Default icon + label color role for capped widgets; fixed hex colors are also supported.
capsule_paddingnumber6Inner padding in logical pixels before scale is applied (clamped 0–48).
capsule_opacitynumber1.0Capsule background opacity (0.0–1.0).
capsule_borderstring(omitted)Color role for the capsule border; fixed hex colors are also supported. If omitted, no border by default. If present as "", no border.

Set under [widget.<name>]:

SettingTypeDefaultDescription
scalenumber1.0Multiplies the owning bar’s scale for this widget only (clamped 0.2–2.5).
capsulebool(from bar)Omit to inherit bar flag; false disables; true forces on.
capsule_radiusnumber(from bar)Per-widget corner radius (0–80). Omit to inherit; omit at every level for automatic pill radius.
capsule_fillstring(from bar)Capsule background color role; fixed hex colors are also supported.
capsule_foregroundstring(from bar)Icon + label color role when capsule is visible; fixed hex colors are also supported. color takes priority over this.
capsule_paddingnumber(from bar)Per-widget inner padding (0–48).
capsule_opacitynumber(from bar)Per-widget capsule background opacity.
capsule_borderstring(from bar)Color role for the capsule border; fixed hex colors are also supported. Omit to inherit bar policy. Present but empty/whitespace-only = no border.
colorstring(unset)Icon + label color role with or without capsule; fixed hex colors are also supported. Resolution order: colorcapsule_foreground → built-in defaults.
icon_colorstring(unset)Overrides color for icons only. Resolution order for icons: icon_colorcolorcapsule_foreground → built-in defaults. Labels always use color.

Prefer color role names so widget styling follows the active palette. Role names use snake_case (e.g. on_surface, surface_variant, secondary). Use fixed hex colors only when you deliberately want a non-palette color; hex may use #RGB, #RGBA, #RRGGBB, or #RRGGBBAA. Invalid role names or malformed hex colors are treated as config errors.

The capsule is hidden automatically when a widget reports no visible ink (empty tray, absent battery, invisible root). Subclasses may override Widget::shouldShowBarCapsule(). The workspaces widget reuses its resolved capsule_radius for workspace pills. The taskbar also reuses its resolved capsule_radius for workspace group capsules when group_by_workspace = true, even if the taskbar’s outer widget capsule is disabled.

[bar.main]
capsule = true
capsule_fill = "surface_variant"
capsule_opacity = 0.9
capsule_border = "outline"
# Accent bar: primary fill + matching text
[bar.accent]
capsule = true
capsule_fill = "primary"
capsule_foreground = "on_primary"
capsule_padding = 10
capsule_radius = 8.0
[widget.volume]
capsule_fill = "secondary"
capsule_radius = 0.0 # square this widget's capsule
capsule_border = "" # no border on this widget
[widget.spacer]
type = "spacer"
capsule = false

A capsule group is a container that holds several widgets and renders them inside one shared capsule with a single common style. A group is a real item in the lane — its members live inside it, so inserting another widget elsewhere can never split it. Grouping is managed entirely from the Settings GUI:

  1. Open Settings → Bar → Bar Widgets.
  2. Drag one widget onto the middle of another (using its drag handle) to instantly create a group from the two. Or tick the checkbox on two or more adjacent widgets and click Group. Either way they move into one group container and a style editor opens.
  3. Edit the group’s background, border, foreground, padding, radius, and opacity — every member updates together.
  4. Drag a widget onto the group to add it; drag a member out, or use its eject button, to remove it. Reorder members by dragging them within the group. Drag the group’s own handle to move the whole group along the lane or into another lane.
  5. To dissolve a group, open its style editor and click Ungroup (its members return to the lane in place).

When dragging a widget, hovering the middle of another widget combines them into a group, while hovering the top/bottom edge drops it between widgets as a normal reorder.

Under the hood each group is one [[bar.<name>.capsule_group]] table holding an ordered members list plus the shared style, and the lane references the group with a single group:<id> token. You normally do not write these by hand:

[bar.main]
end = ["clock", "group:g1", "session"] # the group occupies one lane slot
[[bar.main.capsule_group]]
id = "g1"
members = ["network", "bluetooth", "volume"]
fill = "surface_variant"
padding = 6.0
opacity = 0.9
# border, foreground, radius are optional; omit border for no outline

A group’s style can be overridden per monitor by declaring [[bar.<name>.monitor.<match>.capsule_group]] tables with matching id values. A reused widget name (e.g. several spacer instances) can appear in different groups independently — membership is positional, not a per-widget flag.