9.4 KiB
Omarchy theming
Omarchy themes live under themes/<name>/ in the source tree (installed at
/usr/share/omarchy/themes/<name>/), with optional user themes under
~/.config/omarchy/themes/<name>/. A theme normally starts with a
colors.toml; Omarchy generates the active theme files from
default/themed/*.tpl when omarchy-theme-set <name> runs.
Theme activation flow
omarchy-theme-set <name> builds a clean staging directory at
~/.local/state/omarchy/current/next-theme:
- Copy the first-party theme from
themes/<name>/. - Overlay any user theme files from
~/.config/omarchy/themes/<name>/. - If needed, generate
colors.tomlfromalacritty.toml. - Run
omarchy-theme-set-templatesto render templates into the staging theme. - Move the staging theme into
~/.local/state/omarchy/current/theme, write~/.local/state/omarchy/current/theme.name, and notify the running shell.
Template rendering only happens when the staged theme has colors.toml.
Existing files are never overwritten by a template, so a hand-written
themes/<name>/shell.toml or hyprland.lua wins over
default/themed/shell.toml.tpl or hyprland.lua.tpl.
User templates in ~/.config/omarchy/themed/*.tpl are processed before the
built-in templates. If a user template has the same output filename as a
built-in template, the built-in output is skipped.
colors.toml
colors.toml provides the palette keys used by templates. Common keys are:
bg = "#1a1b26"
fg = "#a9b1d6"
accent = "#7aa2f7"
selection = "#292e42"
red = "#f7768e"
blue = "#7aa2f7"
Any key can be referenced from a template with {{ key }}. The foundational
shell palette is loaded from:
fg— primary readable text colorbg— primary background coloraccent— preferred when present; otherwise some places fall back tocolor4urgent/red/color1
For older user themes and templates, foreground aliases to fg and
background aliases to bg.
The neutral ramp is centered on bg -> bright_fg. Dark themes should read from
darkest to lightest; light themes should read from lightest to darkest. Terminal
and editor cursors use bright_fg; there is no separate cursor palette key.
selection is the text-selection background stop in that ramp; Omarchy derives
selection_background = selection and selection_foreground = bright_fg. Use
omarchy dev theme-preview [theme] to inspect that ramp, including dark_bg,
darker_bg, and a selected-text sample.
Template placeholders
Templates are plain files ending in .tpl. omarchy-theme-set-templates
replaces placeholders with values from colors.toml.
Color placeholders
For a color key such as accent = "#7aa2f7":
| Placeholder | Output |
|---|---|
{{ accent }} |
#7aa2f7 |
{{ accent_strip }} |
7aa2f7 |
{{ accent_rgb }} |
122,162,247 |
Color mixing
mix, mix_strip, and mix_rgb blend two hex colors by a fraction or
percentage:
{{ mix bg fg 15% }}
{{ mix_strip bg accent 0.35 }}
{{ mix_rgb color0 color7 50 }}
Gradient helpers
Some theme keys can be either a solid color or a Hyprland-style gradient:
hyprland_active_border = "rgba(33ccffee) rgba(00ff99ee) 45deg"
Gradient helper placeholders understand those values:
| Helper | Use | Example output |
|---|---|---|
{{ hypr_gradient hyprland_active_border accent }} |
Hyprland Lua config | { colors = { "rgba(33ccffee)", "rgba(00ff99ee)" }, angle = 45 } |
{{ shell_gradient hyprland_active_border accent }} |
shell border tokens | rgba(33ccffee) rgba(00ff99ee) 45deg |
{{ gradient_start hyprland_active_border accent }} |
flat-color-only consumers | #33ccff |
The second argument is a fallback. For example,
{{ shell_gradient hyprland_active_border accent }} means: use
hyprland_active_border if the theme defines it; otherwise use accent.
The helper does not choose the first color unless you use gradient_start.
shell.toml
shell.toml contains shell surface roles, control states, spacing, typography,
and bar sizing. The default generated file comes from
default/themed/shell.toml.tpl.
Themes can override the entire generated file by shipping shell.toml, or just
one section by shipping shell.<section>.toml. For example,
shell.lock.toml replaces only the [lock] section after the default
shell.toml has been generated:
text = "#ffffff"
placeholder = "#ffffff"
border = "#ffffff"
The filename decides the target section, so the [lock] header is optional.
The running shell reads shell.toml into two QML singletons:
Colorfor palette and surface roles likeColor.menu.border.Stylefor controls, spacing, font scale, corner radius, and bar sizing.
Borders
Shell border tokens accept either a solid color or a gradient in the same key:
[notifications]
border = "#7aa2f7"
or:
[notifications]
border = "rgba(33ccffee) rgba(00ff99ee) 45deg"
Do not add a separate border-gradient key for new themes. The parser still
accepts border-gradient and *-border-gradient for compatibility with older
configs, but the canonical form is the border key itself.
Border alphas apply to solid borders and to every gradient stop:
[notifications]
border = "rgba(33ccffee) rgba(00ff99ee) 45deg"
border-alpha = 0.8
If a color stop already includes alpha, the stop alpha and border-alpha are
combined.
Border widths
Border widths accept CSS-style lists:
border-width = 2 # all sides
border-width = "2 4" # top/bottom, right/left
border-width = "2 4 6" # top, right/left, bottom
border-width = "2 4 6 8" # top, right, bottom, left
Per-side keys override the list:
[notifications]
border-width = 2
border-width-left = 6
That gives notifications a 2px border on the top, right, and bottom, and a 6px left edge.
State-specific borders follow the same pattern. A selected menu row can use a different width from the card border:
[menu]
selected-border = "accent"
selected-border-width = "1 1 1 4"
For state-specific surfaces such as lock and polkit, the token name prefixes the width key:
[lock]
border-active = "rgba(33ccffee) rgba(00ff99ee) 45deg"
border-active-width-left = 6
Control borders
[controls] governs shared controls such as buttons, dropdowns, text fields,
toggles, and cursor rows. Each state has a fill color, optional border value,
border width, and border alpha:
[controls]
normal-color = "#a9b1d6"
normal-border = "#a9b1d6"
normal-border-width = 1
normal-border-alpha = 0.4
hover-cursor-color = "#a9b1d6"
hover-cursor-border = "#a9b1d6"
hover-cursor-border-width = 1
hover-cursor-border-alpha = 0.25
The *-border keys can also be gradients:
[controls]
focus-border = "rgba(33ccffee) rgba(00ff99ee) 45deg"
focus-border-width = "2 2 2 4"
Set a border width to 0 to keep the fill but remove that state border.
Surface sections
Common shell sections include:
[bar][controls][popups][tooltip][notifications][launcher][menu][polkit][lock][image-picker][spacing][font]
Clipboard and emojis inherit menu tokens. Popups are used by bar flyouts, dropdowns, OSD, and popup cards.
QML border API
Plugin and shell QML should use BorderSurface for theme-aware borders:
import qs.Commons
import qs.Ui
BorderSurface {
color: Color.popups.background
borderSpec: Border.surfaceSpec("popups", "border", Color.popups.border, 2)
padding: Style.spacing.popupPadding
Item {
anchors.fill: parent
anchors.topMargin: parent.contentTopInset
anchors.rightMargin: parent.contentRightInset
anchors.bottomMargin: parent.contentBottomInset
anchors.leftMargin: parent.contentLeftInset
}
}
Use Border.surfaceSpec(section, token, fallbackColor, fallbackWidth) for
shell theme tokens, Border.controlSpec(state, foreground, accent) for shared
controls, and Border.flat(color, width) for a deliberate local border that
should not be overridden by the active theme. Color.<section>.border is the
flat first-stop color for consumers that cannot render full border specs.
Hyprland templates
Hyprland theme output is generated from default/themed/hyprland.lua.tpl.
Use hypr_gradient for border values because Hyprland's Lua config wants a
Lua string for solid colors and a Lua table for gradients:
local active_border_color = {{ hypr_gradient hyprland_active_border accent }}
For a solid fallback this renders:
local active_border_color = "#7aa2f7"
For a gradient it renders:
local active_border_color = { colors = { "rgba(33ccffee)", "rgba(00ff99ee)" }, angle = 45 }
Adding or overriding theme files
- Add palette values to
themes/<name>/colors.toml. - Prefer generated files when the theme can be expressed with templates.
- Add a hand-written file in
themes/<name>/only when that theme needs to override the generated output entirely. - Add a new built-in template under
default/themed/<file>.tplwhen every theme should generate that file. - Add a user-wide template under
~/.config/omarchy/themed/<file>.tplwhen a local customization should apply across themes.
When changing templates or theme helpers, run focused tests such as:
./test/cli
./test/shell