How Surface Roles and Sizing Are Defined in Omarchy Themes

Surface roles and sizing in Omarchy themes are defined through a strict separation of concerns: semantic surface roles map colors to UI components via shell.toml and theme-specific colors.toml files, while layout sizing tokens (corner radius, borders, spacing) are centralized in shell.toml and applied through the Style QML module.

Omarchy, the open-source desktop environment from Basecamp, implements a sophisticated theming architecture that decouples visual appearance from geometric structure. Understanding how surface roles and sizing in Omarchy themes interact allows developers to customize colors without breaking layout consistency. This system ensures that themes supply only color values while the shell maintains uniform spacing, borders, and corner radii across all surfaces.

How Surface Roles Map Colors to UI Components

Surface roles act as semantic bridges between abstract color palettes and concrete UI elements. Instead of hardcoding hex values in QML components, Omarchy uses role-based identifiers like menu or popups that resolve to theme-specific colors at runtime.

Configuration in shell.toml

The central shell.toml file defines the surface role mappings in a dedicated [surfaceRoles] section. Each role specifies which color keys from the theme palette apply to specific UI surfaces, ensuring consistent theming across menus, popups, and status bars.


# shell.toml (excerpt)

[surfaceRoles]
menu = { 
    background = "Color.menu.background", 
    text = "Color.menu.foreground", 
    border = "Color.menu.border", 
    scrim = "Color.menu.scrim" 
}
popups = { 
    background = "Color.popups.background", 
    text = "Color.popups.foreground", 
    border = "Color.popups.border" 
}

These entries tell the runtime which palette keys correspond to each surface's background, text, border, and overlay (scrim) properties.

Theme-Specific Color Definitions

Individual themes provide concrete color values in themes/<name>/colors.toml. The keys referenced in shell.toml's surface roles must exist in this file for the mapping to resolve correctly.


# themes/nord/colors.toml

[menu]
background = "#2e3440"
foreground = "#d8dee9"
border = "#4c566a"
scrim = "#00000080"

[popups]
background = "#3b4252"
foreground = "#eceff4"
border = "#434c5e"

At startup, shell/shell.qml initializes the color system by calling Color.loadColors(colorsRaw) and Color.loadShell(shellRaw), which parses these definitions into the Color QML module.

Centralized Sizing Tokens in shell.toml

While themes control colors, geometric properties remain consistent across all themes through centralized sizing tokens. This ensures that switching themes never alters button sizes, spacing, or border thicknesses unless explicitly intended.

Control Sizing Configuration

The [controlSizing] section in shell.toml defines the numeric tokens that govern UI geometry:


# shell.toml

[controlSizing]
cornerRadius = 6
borderWidth = 2
spacing = 8

These values apply universally to all surfaces unless overridden by specific component logic. The shell/Commons/Style.qml file reads these tokens during initialization and exposes them as properties like Style.cornerRadius and Style.borderWidth.

Style Helper Functions in Style.qml

Beyond raw values, shell/Commons/Style.qml provides semantic helper functions that combine sizing tokens with color roles to generate complete styling specifications. These functions include Style.normalFillFor, Style.hoverFillFor, and Style.normalBorderFor, which apply the appropriate border width and corner radius defined in shell.toml to any given surface color.

When a component calls Style.normalBorderFor(Color.menu.foreground, …), the function applies the borderWidth token from shell.toml while using the theme-specific foreground color from the surface role mapping.

Consuming Surface Roles and Sizing in QML Components

UI components integrate both systems by referencing the Color module for theme-specific values and the Style module for geometric constraints. This two-source approach ensures that a button automatically reflects the active theme's palette while maintaining the shell's standardized proportions.

// Example: Menu container implementation
Rectangle {
    color: Color.menu.background
    radius: Style.cornerRadius
    border.width: Style.borderWidth
    border.color: Color.menu.border

    Text {
        text: model.label
        color: Color.menu.foreground
        anchors.margins: Style.spacing
    }
}

In this pattern, Color.menu.background resolves to the hex value defined in themes/nord/colors.toml (or whichever theme is active), while Style.cornerRadius and Style.borderWidth pull from the [controlSizing] section of shell.toml. This decoupling allows designers to swap color palettes instantly without risking layout regression.

Summary

  • Surface roles in Omarchy provide semantic mappings (menu, popups, bar) that connect abstract UI elements to theme-specific color values defined in colors.toml.
  • The shell.toml file serves dual duty: it defines role mappings in [surfaceRoles] and geometric constants in [controlSizing].
  • The Color QML module (shell/Commons/Color.qml) loads and exposes color values as Color.<surface>.<property> for use throughout the UI.
  • The Style QML module (shell/Commons/Style.qml) consumes sizing tokens from shell.toml to provide consistent geometry via properties and helper functions.
  • This architecture ensures theme authors modify only colors while the shell enforces consistent spacing, borders, and corner radii.

Frequently Asked Questions

What is the difference between surface roles and color palettes in Omarchy?

Color palettes are simple key-value mappings in themes/<name>/colors.toml that assign hex values to names like menu.background. Surface roles are semantic definitions in shell.toml that group these palette entries into functional UI categories (menu, popups, bar) and specify which palette key serves as the background, text, border, or scrim for that surface. The surface roles provide the contract that QML components use, while palettes provide the concrete values.

Where are corner radius and border width defined in Omarchy themes?

Corner radius and border width are not defined in individual theme folders. Instead, they live in the [controlSizing] section of the global shell.toml file as cornerRadius and borderWidth tokens. The shell/Commons/Style.qml module reads these values and exposes them to all QML components, ensuring consistent geometry regardless of which color theme is active.

How does the Color QML module load surface role definitions?

During initialization, shell/shell.qml calls Color.loadColors(colorsRaw) to parse the theme's colors.toml and Color.loadShell(shellRaw) to parse the shell.toml surface role mappings. The shell/Commons/Color.qml implementation merges these definitions, building objects like Color.menu and Color.popups that expose background, foreground, border, and scrim properties to the rest of the application.

Can I override sizing tokens per theme, or only per shell?

According to the current architecture implemented in basecamp/omarchy, sizing tokens are shell-wide constants defined exclusively in shell.toml. Themes are limited to color definitions in their respective colors.toml files. To change sizing values, you must modify [controlSizing] in shell.toml, which affects all themes uniformly, maintaining Omarchy's design principle of consistent geometry across color variations.

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 →