How Shell Theme Tokens (Color, Style, Border) Work in Omarchy QML
Omarchy’s shell UI uses three QML modules—Color.qml, Style.qml, and Border.qml—to provide theme tokens that propagate visual changes automatically from TOML configuration without hard-coding values in components.
The Omarchy desktop environment by Basecamp implements a fully theme-driven interface using Qt Modeling Language (QML). Instead of embedding literal color values or border widths directly into UI components, the shell references abstract shell theme tokens defined in three core modules. This architecture ensures that modifying a single entry in the shell.toml configuration instantly updates the entire interface.
The Three Core Theme Token Modules
Omarchy separates visual concerns into three distinct QML singletons located in shell/Commons/. Each module handles a specific layer of the theming stack, from raw hex values to calculated interactive states.
Color.qml – The Palette Registry
Color.qml serves as the central registry for every color value used throughout the shell. At startup, it ingests raw definitions from the TOML configuration file and exposes them as typed properties such as Color.accent, Color.menu.background, or Color.foreground. These properties are organized by semantic section (menus, panels, bars) so that components reference intent rather than specific values.
Style.qml – State-Derived Color Logic
Style.qml provides pure functions that calculate colors for interactive states—hover, selected, disabled, and active—based on the raw palette from Color.qml. Rather than hard-coding transparency or shading math in individual components, the shell uses helpers like Style.hoverFillFor(base, accent) and Style.selectedFillFor(base, accent) to generate consistent visual feedback across all UI elements.
Border.qml – Border Specifications
Border.qml constructs reusable border specifications containing width, color, and radius values. Functions such as Border.controlSpec(state, bg, accent) and Border.surfaceSpec(section, element, baseColor, width) return structured objects that QML elements consume via a borderSpec property. This ensures that borders remain consistent with the current theme state while supporting contextual variations for controls, surfaces, and flat decorative lines.
Loading Tokens at Startup
When the shell initializes, it reads the user’s shell.toml file and hydrates the token modules before any UI elements render. In shell/shell.qml, the initialization sequence calls:
// shell/shell.qml (lines 884-885)
Color.loadColors(colorsRaw)
Color.loadShell(shellRaw)
The loadColors() method populates the base palette, while loadShell() applies shell-specific overrides for menus, panels, and bars defined in the configuration. Once loaded, these values remain immutable until the next session, ensuring consistent rendering performance.
Using Style Helpers for Interactive States
Components never calculate hover or selection colors manually. Instead, they invoke Style helpers that blend the current foreground with the accent color. For example, in the power panel plugin:
// plugins/panels/power/Panel.qml (line 292)
color: Style.hoverFillFor(root.bar.foreground, Color.accent)
Style.hoverFillFor() returns a semi-transparent blend of the base (foreground) and accent colors, creating the standard highlight effect. For selected states, developers use Style.selectedFillFor(base, accent), while Style.normalBorderFor(color) generates appropriate outline colors for disabled or inactive elements.
Defining Borders with Border Specifications
Borders in Omarchy are not primitive values but structured specifications generated by Border.qml. The weather panel demonstrates the typical pattern:
// plugins/panels/weather/Panel.qml (line 1892)
borderSpec: Border.controlSpec("normal", root.bar.foreground, Color.accent)
Border.controlSpec() accepts a state string ("normal", "hover", or "selected"), a background color, and an accent color, returning a complete border definition. For simpler cases, Border.flat(color, width) creates a solid line without radius, suitable for dividers or separators. Complex surfaces like lock screens use Border.surfaceSpec(section, element, baseColor, width) to pull context-aware values from the theme.
Complete Implementation Example
The following example demonstrates how a button combines all three token types to react to mouse events without hard-coding any color values:
Rectangle {
id: button
color: Style.hoverFillFor(root.bar.foreground, Color.accent)
borderSpec: Border.controlSpec("normal", root.bar.foreground, Color.accent)
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: button.state = "hover"
onExited: button.state = "normal"
}
}
- Color supplies the raw palette via
root.bar.foregroundandColor.accent. - Style computes the hover background dynamically.
- Border provides the correct thickness and color for the current state.
Because every component follows this token pattern, changing Color.accent in shell.toml immediately updates every button, panel, and menu border throughout the interface.
Summary
- Color.qml acts as the single source of truth for raw color values loaded from
shell.tomlat startup (lines 884-885 ofshell.qml). - Style.qml exposes helper functions like
hoverFillFor()andselectedFillFor()that derive interactive state colors from the base palette. - Border.qml generates structured border specifications via
controlSpec()andflat(), ensuring consistent outlines across the shell. - Components reference tokens rather than literals, enabling complete theme changes without modifying QML source code.
Frequently Asked Questions
How do I change the accent color in Omarchy?
Edit the shell.toml configuration file and modify the accent value under the [colors] section. When the shell restarts, Color.loadColors() in shell/Commons/Color.qml ingests the new value, and every component referencing Color.accent updates automatically.
What is the difference between Color and Style tokens?
Color tokens represent static, raw palette values (e.g., Color.menu.background), while Style tokens are dynamic functions that calculate colors for specific UI states such as hover or selection (e.g., Style.hoverFillFor()). Style helpers always consume Color tokens as inputs.
Can I customize border widths while using theme tokens?
Yes. Functions like Border.controlSpec() and Border.surfaceSpec() accept explicit width parameters that override defaults, while still pulling colors from the theme. For fixed-width decorative lines, use Border.flat(color, width) to specify exact pixel dimensions.
Where are theme tokens initialized in the codebase?
Theme tokens initialize in shell/shell.qml at lines 884-885, where Color.loadColors() and Color.loadShell() parse the TOML configuration and populate the Color, Style, and Border singletons before the UI renders.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →