How Omarchy Integrates Color and Style Singletons for Theme-Consistent UI
Omarchy uses Quickshell QML singletons—Color and Style declared in shell/Commons/qmldir—to provide a single source of truth for theme colors and design tokens across the entire desktop environment.
Omarchy's visual architecture centers on two critical singletons that eliminate state duplication and enable instant theme switching. This article explains how the Color and Style singletons are registered, loaded, and consumed throughout the shell, with specific reference to the omacom/omarchy source code.
What Are the Color and Style Singletons?
Omarchy defines three core QML singletons in shell/Commons/qmldir:
singleton Border 1.0 Border.qml
singleton Color 1.0 Color.qml
singleton Style 1.0 Style.qml
singleton Util 1.0 Util.qml
- Color (
shell/Commons/Color.qml): Exports theme-derived color palettes and surface-specific color roles - Style (
shell/Commons/Style.qml): Provides typographic, spacing, and widget-level design tokens
Both use pragma Singleton to guarantee exactly one instance per session. This design pattern ensures that when a theme changes, every UI element updates simultaneously without re-instantiating components.
How the Color Singleton Works
The Color singleton parses theme configuration and exposes reactive color properties. Located at shell/Commons/Color.qml, it begins with:
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
import "BorderGeometry.js" as Geometry
Theme Parsing and Color Resolution
Color reads colors.toml from the active theme directory and shell.toml from generated configuration. It exposes:
shellValues: Flat dictionary mapping"section.key"strings to raw TOML values- Helper functions:
pick(),pickAlpha(),flatColor(),composed()
Surface-specific color objects use nested QtObject declarations:
readonly property QtObject bar: QtObject {
property color background: root.composed("bar.background",
"bar.background-alpha",
root.background, 1.0)
property color text: root.pick("bar.text", root.foreground)
property color active: root.pick("bar.active", root.urgent)
}
This creates type-safe, theme-aware color roles that components import:
import "Color.qml" as Color
Rectangle {
color: Color.bar.background
Text {
color: Color.bar.text
}
}
Theme switching updates shellValues, triggering QML's reactive binding system to re-evaluate every color reference automatically.
How the Style Singleton Works
The Style singleton (shell/Commons/Style.qml) follows identical singleton semantics:
pragma Singleton
import QtQuick
QtObject {
readonly property int borderRadius: 6
readonly property int padding: 8
readonly property string fontFamily: "Ubuntu"
readonly property int fontSize: 12
readonly property real animationDuration: 0.15
}
Design Token Consumption
Components reference Style properties directly for layout consistency:
import "Style.qml" as Style
Text {
font.family: Style.fontFamily
font.pixelSize: Style.fontSize
leftPadding: Style.padding
}
Because Style is a singleton, typography and spacing changes propagate instantly without component recompilation or manual refresh calls.
Integration Architecture
Omarchy's singleton integration follows three phases:
- Registration –
shell/Commons/qmldirdeclares singletons; Quickshell loads them into the QML engine's import path duringshell/shell.qmlinitialization - Binding – UI components bind to
Color.*andStyle.*properties; QML's property system maintains reactive dependencies - Mutation – Theme changes update singleton internal state; all bound properties re-evaluate simultaneously
Why Singletons Instead of Regular Imports?
Regular QML imports create new instances per import site. True singletons (via pragma Singleton + qmldir registration) share identical object identity across all importers. This eliminates:
- Per-component state duplication
- Synchronization bugs between panels
- Race conditions during theme transitions
Practical Code Examples
Complete Plugin Using Both Singletons
import QtQuick
import "Color.qml" as Color
import "Style.qml" as Style
Rectangle {
width: 200
height: 40
color: Color.bar.background
radius: Style.borderRadius
Text {
anchors.centerIn: parent
text: "Omarchy"
color: Color.bar.text
font.family: Style.fontFamily
font.pixelSize: Style.fontSize
}
}
Layout with Style Tokens
import "Style.qml" as Style
Row {
spacing: Style.padding
Rectangle {
width: 30; height: 30;
color: "red"
radius: Style.borderRadius
}
Rectangle {
width: 30; height: 30;
color: "green"
radius: Style.borderRadius
}
}
Theme Switching Implementation
In shell/shell.qml, theme changes propagate through the Color singleton:
Connections {
target: OmarchyTheme
onThemeChanged: {
Color.shellValues = OmarchyTheme.loadShellToml()
// All Color.* bindings refresh automatically
}
}
Key Implementation Files
| File | Purpose | Location |
|---|---|---|
shell/Commons/qmldir |
Singleton registration | shell/Commons/qmldir |
shell/Commons/Color.qml |
Theme color parsing and surface roles | shell/Commons/Color.qml |
shell/Commons/Style.qml |
Typography and spacing tokens | shell/Commons/Style.qml |
shell/shell.qml |
Entry point, theme loading | shell/shell.qml |
docs/theming.md |
Theme configuration format | docs/theming.md |
Summary
- Color and Style singletons in Omarchy provide centralized, reactive theme properties via
pragma Singletonandshell/Commons/qmldirregistration - Color parses
colors.tomlandshell.toml, exposing surface-specific color objects with helper functions for gradient, alpha, and fallback handling - Style exports immutable design tokens for typography, spacing, radius, and animation timing
- QML's reactive binding system ensures instant UI updates when singleton state changes, eliminating manual refresh logic
- True singleton semantics prevent state duplication and race conditions across multiple panels and plugins
Frequently Asked Questions
What makes Omarchy's Color and Style singletons different from regular QML modules?
Regular QML imports create distinct instances per import location. Omarchy's use of pragma Singleton combined with qmldir registration ensures exactly one object identity shared across all importers. This guarantees that updating Color.shellValues immediately affects every component binding to Color.* properties.
How does Omarchy handle theme switching without restarting the shell?
The Color singleton's shellValues property is replaced when OmarchyTheme.loadShellToml() returns new configuration. Because all color properties are computed from shellValues through QML bindings, the reactive system re-evaluates every dependent property automatically. No manual component refresh or shell restart is required.
Can I extend the Style singleton with custom design tokens?
Yes. Since Style.qml is a standard QML singleton, you can add readonly properties following the existing pattern. Changes to shell/Commons/Style.qml propagate to all importers immediately. For theme-specific style overrides, consider adding computed properties that read from Color.shellValues similar to how Color handles theme-dependent values.
Where does Omarchy load theme data from?
The Color singleton reads themes/*/colors.toml for color definitions and generated shell.toml for shell-specific overrides. These paths are resolved relative to the active theme directory, which shell/shell.qml configures during startup. The docs/theming.md file documents the complete TOML schema for theme authors.
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 →