Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The Solver

The Solver is the engine that powers the color system. It takes your high-level Intent and turns it into precise CSS Tokens.

You can interact with the Theme Builder in two ways:

  1. The UI: The interactive web interface (for exploration).
  2. The CLI: The color-system command line tool (for production).

Both use the exact same “Solver” logic under the hood.

The “Theme Builder” Model

To understand the solver, it helps to think about the controls you see in the Theme Builder UI. The solver is simply the code that runs every time you move a slider or add a surface.

1. Anchors: Defining the Playing Field

In the Theme Builder, you set the Anchors. These are the boundaries of your color system.

Page Anchors

Defines the lightness range for the "Page" polarity.

Start (Background) 0.98
End (Elevated) 0.12

The solver takes these values and asks: “Can I fit readable text inside this range?” If the answer is “No” (and the anchor is adjustable), the solver moves the slider for you until the text is readable.

2. Surfaces: The “Steps”

In the Theme Builder, you add Surfaces to a list.

Surfaces

Page .surface-page Passes
<!-- Surface Item 2 -->
<div class="surface-card docs-p-2 docs-rounded docs-border" style="display: flex; align-items: center; gap: 1rem;">
  <span class="text-strong" style="flex: 1;">Card</span>
  <code class="text-subtle">.surface-card</code>
  <span style="font-size: 0.8em; background: #dcfce7; color: #166534; padding: 2px 6px; border-radius: 4px;">Passes</span>
</div>

<!-- Surface Item 3 -->
<div class="surface-card docs-p-2 docs-rounded docs-border" style="display: flex; align-items: center; gap: 1rem;">
  <span class="text-strong" style="flex: 1;">Sidebar</span>
  <code class="text-subtle">.surface-sidebar</code>
  <span style="font-size: 0.8em; background: #dcfce7; color: #166534; padding: 2px 6px; border-radius: 4px;">Passes</span>
</div>

The solver’s job is to place these surfaces evenly between your Start and End anchors. It doesn’t just divide the lightness evenly (e.g., 10%, 20%, 30%). It divides the Contrast Space evenly. This ensures that the visual “step” from Page to Card looks the same as the step from Card to Sidebar.

Why Contrast Space?

If we just divided the lightness values evenly (Linear Lightness), the steps would look uneven to the human eye. Dark colors bunch up, and light colors spread out. By dividing by Contrast (Linear Perception), every step feels visually consistent.

Linear Lightness (Bad)
Mathematically equal steps (-10%). To the eye, the lighter steps feel too jumpy, while the darker steps blend together.
Linear Contrast (Good)
Perceptually equal steps. The solver makes larger adjustments to the darker shades to ensure every step feels distinct.

3. The Result: Generated Tokens

Finally, the solver outputs the CSS tokens that the Theme Builder (and your app) uses.

Generated CSS

--lightness-surface-page: light-dark(0.98, 0.12);
--lightness-surface-card: light-dark(0.95, 0.15);
--lightness-surface-sidebar: light-dark(0.92, 0.18);

The Pipeline

When you run pnpm solve (or change a setting in the Builder), this pipeline executes:

  1. Hydrate: Read your config.
  2. Adjust Anchors: Ensure the range supports High Contrast text.
  3. Distribute: Calculate the target contrast for each surface.
  4. Solve Lightness: Use binary search to find the exact lightness value that hits that contrast target.
  5. Solve Text: Find the text colors that sit accessibly on top of those surfaces.
  6. Generate: Write the CSS.