# How Surface Roles and Sizing Are Defined in Omarchy Themes

> Learn how Omarchy themes define surface roles and sizing with clear separation of concerns using shell.toml and colors.toml for efficient UI theming.

- Repository: [37signals/omarchy](https://github.com/basecamp/omarchy)
- Tags: how-to-guide
- Published: 2026-08-21

---

**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`](https://github.com/basecamp/omarchy/blob/main/shell.toml) and theme-specific [`colors.toml`](https://github.com/basecamp/omarchy/blob/main/colors.toml) files, while layout sizing tokens (corner radius, borders, spacing) are centralized in [`shell.toml`](https://github.com/basecamp/omarchy/blob/main/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`](https://github.com/basecamp/omarchy/blob/main/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.

```toml

# 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`](https://github.com/basecamp/omarchy/blob/main/shell.toml)'s surface roles must exist in this file for the mapping to resolve correctly.

```toml

# 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`](https://github.com/basecamp/omarchy/blob/main/shell.toml) defines the numeric tokens that govern UI geometry:

```toml

# 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`](https://github.com/basecamp/omarchy/blob/main/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`](https://github.com/basecamp/omarchy/blob/main/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.

```qml
// 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`](https://github.com/basecamp/omarchy/blob/main/themes/nord/colors.toml) (or whichever theme is active), while `Style.cornerRadius` and `Style.borderWidth` pull from the `[controlSizing]` section of [`shell.toml`](https://github.com/basecamp/omarchy/blob/main/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`](https://github.com/basecamp/omarchy/blob/main/colors.toml).
- The [`shell.toml`](https://github.com/basecamp/omarchy/blob/main/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`](https://github.com/basecamp/omarchy/blob/main/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`](https://github.com/basecamp/omarchy/blob/main/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`](https://github.com/basecamp/omarchy/blob/main/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`](https://github.com/basecamp/omarchy/blob/main/colors.toml) and `Color.loadShell(shellRaw)` to parse the [`shell.toml`](https://github.com/basecamp/omarchy/blob/main/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`](https://github.com/basecamp/omarchy/blob/main/shell.toml). Themes are limited to color definitions in their respective [`colors.toml`](https://github.com/basecamp/omarchy/blob/main/colors.toml) files. To change sizing values, you must modify `[controlSizing]` in [`shell.toml`](https://github.com/basecamp/omarchy/blob/main/shell.toml), which affects all themes uniformly, maintaining Omarchy's design principle of consistent geometry across color variations.