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:

  1. Registration – shell/Commons/qmldir declares singletons; Quickshell loads them into the QML engine's import path during shell/shell.qml initialization
  2. Binding – UI components bind to Color.* and Style.* properties; QML's property system maintains reactive dependencies
  3. 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 Singleton and shell/Commons/qmldir registration
  • Color parses colors.toml and shell.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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →