-
Notifications
You must be signed in to change notification settings - Fork 1
Styling
UnixNotis uses GTK4 CSS. Theme files live in the config directory and are hot-reloaded when edited.
The important part is that the theme path is now additive:
- old themes still work through the legacy GTK color path
- newer GTK setups can also use modern custom properties and
var(...) -
css-checkunderstands that newer path instead of treating it like blanket bad input
This means theme authors can keep writing safe legacy CSS, or opt into newer GTK CSS features for cleaner and more reusable theme files.
Theme files are stored under $XDG_CONFIG_HOME/unixnotis:
-
base.css: palette and shared tokens -
panel.css: control-center panel rules -
popup.css: toast popup rules -
widgets.css: toggle, stat, card, and shared widget rules -
media.css: media-widget-only rules loaded abovewidgets.css
If a file is missing, a default template is created on startup.
Panel UI:
base.csspanel.csswidgets.cssmedia.css
Popup UI:
base.csspopup.css
Later files override earlier rules.
base.css defines the palette and theme hooks. Adjust these first to re-skin the UI
without touching component rules.
Common tokens:
-
unixnotis-surface-base,unixnotis-card-base -
unixnotis-text,unixnotis-muted,unixnotis-accent unixnotis-panel-grad-1/2/3unixnotis-notification-bg-1/2
UnixNotis now supports a newer GTK CSS path on runtimes that can parse it, while keeping the old theme path intact for existing configs.
That adds three real benefits:
- Reusable values
- Custom properties make it possible to define a size or color once and reuse it across panel cards, popups, toggles, media rows, and custom selectors
- Cleaner math
-
calc(...)can be used for GTK size math when the property accepts it, which makes spacing and sizing easier to tune without hardcoding every number
- More stable theme targets
- The UI now exposes more shared hook classes for panel cards, popup cards, and media cards, so themes can react to widget state directly instead of guessing from text or fragile selector chains
The goal is to support more of what newer GTK already supports, without breaking existing themes.
Legacy-safe path:
@define-color- stock class names
- direct fixed values like
12px,16px,alpha(...)
Modern additive path:
:root { --unixnotis-* }var(--unixnotis-...)calc(...)- newer shared hook classes on panel, popup, and media widgets
Existing configs do not need to change. The modern path is there for themes that want it.
The [theme] section in config.toml controls alpha and geometry values applied at
runtime:
[theme]
border_width = 1
card_radius = 16
surface_alpha = 0.88
surface_strong_alpha = 0.96
card_alpha = 0.94
shadow_soft_alpha = 0.30
shadow_strong_alpha = 0.55Those config values now feed both:
- the legacy color override layer
- the newer
--unixnotis-*custom-property layer on supported GTK runtimes
That matters because a single config knob can now drive both old and new theme styles without forcing users to rewrite their CSS.
When the GTK runtime supports custom properties, UnixNotis emits shared --unixnotis-*
values that themes can consume directly.
Examples:
--unixnotis-border-width--unixnotis-card-radius--unixnotis-card-alpha--unixnotis-panel-header-radius--unixnotis-panel-header-padding--unixnotis-panel-card-padding-y--unixnotis-panel-card-padding-x--unixnotis-panel-search-min-height--unixnotis-panel-search-padding-x--unixnotis-notification-card-radius--unixnotis-notification-action-padding-y--unixnotis-notification-action-padding-x--unixnotis-popup-card-padding-y--unixnotis-popup-card-padding-x--unixnotis-toggle-min-width--unixnotis-toggle-min-height--unixnotis-stat-card-radius--unixnotis-stat-card-padding-y--unixnotis-stat-card-padding-x--unixnotis-info-card-radius--unixnotis-info-card-min-height--unixnotis-calendar-radius--unixnotis-media-art-size--unixnotis-media-row-gap--unixnotis-accent-color--unixnotis-card-color
These values are useful because themes no longer have to duplicate the same stock layout numbers in many different rules just to stay aligned with the live UI.
Reuse one value in many places:
:root {
--card-gap: 14px;
}
.unixnotis-panel-card {
padding: var(--unixnotis-panel-card-padding-y) var(--unixnotis-panel-card-padding-x);
border-radius: var(--unixnotis-card-radius);
}
.unixnotis-popup-card {
padding: calc(var(--unixnotis-popup-card-padding-y) + 2px)
var(--unixnotis-popup-card-padding-x);
}
.unixnotis-media-card {
gap: calc(var(--card-gap) - 4px);
}Before this update, that style was either unsupported, noisy in css-check, or much
harder to reason about because the checker could not follow those values correctly.
Panel shell and header:
.unixnotis-panel.unixnotis-panel-header.unixnotis-panel-title.unixnotis-panel-action
Notification list:
-
.unixnotis-group,.unixnotis-group-header .unixnotis-panel-card.unixnotis-stack-ghost
Popup cards:
.unixnotis-popup-card.unixnotis-popup-actions button
Empty state (no notifications):
.unixnotis-empty.unixnotis-empty-label
Widgets:
.unixnotis-quick-controls.unixnotis-quick-slider.unixnotis-toggle-
.unixnotis-toggle-kind-<kind>(added when a togglekindis set) .unixnotis-stat-card.unixnotis-info-card.unixnotis-media-card.unixnotis-media-stack.unixnotis-media-row.unixnotis-media-header.unixnotis-media-body.unixnotis-media-text.unixnotis-media-art-frame.unixnotis-media-nav-strip.unixnotis-media-button-play
UnixNotis now exposes more stable class hooks for real widget state.
Panel shell and panel action hooks:
.unixnotis-panel-window.unixnotis-panel-header-top.unixnotis-panel-title-stack.unixnotis-panel-title-row.unixnotis-panel-search-revealer.unixnotis-widget-stack.unixnotis-widget-revealer.unixnotis-panel-actions.unixnotis-panel-action-group.unixnotis-panel-action.unixnotis-panel-action-content.unixnotis-panel-action-glyph.unixnotis-panel-action-label.unixnotis-panel-action-focus.unixnotis-panel-action-primary.unixnotis-panel-action-muted.unixnotis-panel-action-search.unixnotis-panel-action-close.unixnotis-panel-action-with-icon.unixnotis-panel-action-icon
Panel card hooks:
.unixnotis-panel-card-has-actions.unixnotis-panel-card-no-actions.unixnotis-panel-card-has-body.unixnotis-panel-card-has-summary
Toggle hooks:
.unixnotis-toggle-grid.unixnotis-toggle.unixnotis-toggle-content.unixnotis-toggle-icon.unixnotis-toggle-label.unixnotis-toggle-has-icon.unixnotis-toggle-no-icon
Stat hooks:
.unixnotis-stat-grid.unixnotis-stat-card.unixnotis-stat-header.unixnotis-stat-icon.unixnotis-stat-title.unixnotis-stat-value.unixnotis-stat-card-builtin.unixnotis-stat-card-plugin.unixnotis-stat-card-has-icon.unixnotis-stat-card-no-icon
Info card hooks:
.unixnotis-card-grid.unixnotis-info-card.unixnotis-info-header.unixnotis-info-icon.unixnotis-info-title.unixnotis-info-body.unixnotis-calendar.unixnotis-info-card-calendar.unixnotis-info-card-weather.unixnotis-info-card-mono.unixnotis-info-card-has-icon.unixnotis-info-card-no-icon
Popup card hooks:
.unixnotis-popup-card-has-actions.unixnotis-popup-card-has-body.unixnotis-popup-card-has-icon.unixnotis-popup-card-no-icon.unixnotis-popup-card-has-summary
Media card hooks:
.unixnotis-media-card-has-art.unixnotis-media-card-no-art.unixnotis-media-card-has-artist.unixnotis-media-card-empty-artist.unixnotis-media-card-playing.unixnotis-media-card-paused.unixnotis-media-card-stopped.unixnotis-media-card-single-player.unixnotis-media-card-multi-player
Media shell hooks:
.unixnotis-media-stack.unixnotis-media-stack-player.unixnotis-media-row.unixnotis-media-row-player.unixnotis-media-header.unixnotis-media-body.unixnotis-media-text.unixnotis-media-main.unixnotis-media-meta.unixnotis-media-source.unixnotis-media-position.unixnotis-media-title.unixnotis-media-artist.unixnotis-media-art.unixnotis-media-art-frame.unixnotis-media-controls.unixnotis-media-control-strip.unixnotis-media-action-rail.unixnotis-media-nav-strip.unixnotis-media-nav.unixnotis-media-nav-prev.unixnotis-media-nav-next.unixnotis-media-button.unixnotis-media-button-prev.unixnotis-media-button-play.unixnotis-media-button-next.unixnotis-media-card-player.unixnotis-media-has-title.unixnotis-media-no-title.unixnotis-media-has-source.unixnotis-media-no-source.unixnotis-media-has-position.unixnotis-media-no-position.unixnotis-media-has-controls.unixnotis-media-no-controls.unixnotis-media-has-nav.unixnotis-media-no-nav.unixnotis-media-art-start.unixnotis-media-art-top.unixnotis-media-art-hidden.unixnotis-media-controls-inline.unixnotis-media-controls-bottom.unixnotis-media-controls-side.unixnotis-media-controls-hidden.unixnotis-media-nav-external.unixnotis-media-nav-inline.unixnotis-media-nav-bottom.unixnotis-media-nav-side.unixnotis-media-nav-hidden
Grouped row hooks:
.unixnotis-group.unixnotis-group-row.unixnotis-group-header.unixnotis-group-icon.unixnotis-group-title.unixnotis-group-count.unixnotis-group-chevron.unixnotis-group-row-collapsed.unixnotis-group-row-expanded.unixnotis-group-row-has-icon.unixnotis-group-row-no-icon
Placeholder row hooks:
.unixnotis-empty.unixnotis-empty-label.unixnotis-stack-ghost.unixnotis-stack-ghost-<depth>
Shared state hooks:
.active.critical.empty.playing.stacked
These hooks are one of the biggest customization gains in this update. They let themes react to real UI state directly.
Example:
.unixnotis-media-card.unixnotis-media-card-no-art {
padding-left: calc(var(--unixnotis-media-card-padding-x) + 6px);
}
.unixnotis-media-card.unixnotis-media-card-multi-player .unixnotis-media-position {
color: var(--unixnotis-accent-color);
}
.unixnotis-media-art-top .unixnotis-media-art-frame {
min-width: calc(var(--unixnotis-media-art-size) + 10px);
}
.unixnotis-media-controls-bottom .unixnotis-media-control-strip {
padding-top: 4px;
}
.unixnotis-media-no-source .unixnotis-media-position {
background: alpha(var(--unixnotis-card-color), 0.32);
}
.unixnotis-popup-card.unixnotis-popup-card-no-icon {
padding-left: calc(var(--unixnotis-popup-card-padding-x) + 8px);
}The empty-state text is configurable in two ways:
-
panel.empty_textcontrols the label text. -
panel.empty_offset_topshifts the label down when widgets are visible.
When no widgets are visible, the empty state is centered in the list area.
Example:
.unixnotis-empty-label {
letter-spacing: 0.35em;
font-size: 12px;
text-transform: uppercase;
color: @unixnotis-muted;
}.unixnotis-quick-slider {
border-radius: var(--unixnotis-card-radius);
min-height: calc(var(--unixnotis-toggle-min-width) / 2);
}
.unixnotis-toggle:checked {
background: alpha(@unixnotis-accent, 0.25);
}
.unixnotis-toggle.unixnotis-toggle-kind-wifi:checked {
background: alpha(@unixnotis-accent, 0.25);
}
.unixnotis-toggle.unixnotis-toggle-kind-bluetooth:checked {
background: alpha(@unixnotis-accent-2, 0.25);
}
.unixnotis-panel-card.unixnotis-panel-card-has-summary {
padding-bottom: calc(var(--unixnotis-panel-card-padding-y) + 2px);
}
.unixnotis-media-card.unixnotis-media-card-playing {
border-color: var(--unixnotis-accent-color);
}
.unixnotis-stat-value {
font-weight: 700;
letter-spacing: 0.03em;
}noticenterctl css-check is no longer just a parser wrapper.
It now helps with three different theme-authoring problems:
- Syntax and parser failures
- broken GTK CSS
- Theme wiring problems
- missing active files
- duplicate theme slots
- outside-root theme asset issues
- Layout pressure
- rules that are likely to force the panel wider than expected
- tracked widget sizing that looks unsafe for the configured panel width
It also understands more modern GTK CSS than before. Valid var(...) and calc(...)
usage is no longer treated as a blanket warning just because it looks more web-like.
CSS files are watched for changes and reloaded on write. Large edits are coalesced into a single reload to avoid flicker.