Daybridge Design System
Foundations

Colors

How the Daybridge color system works, from first principles

Why OKLCH?

Most digital color systems use RGB or HSL, but these have a fundamental problem: they don't match how humans actually see color. In HSL, a yellow and a blue at the same "lightness" value will look completely different to your eyes — the yellow appears far brighter. This makes it impossible to build a consistent color palette where every hue feels equally prominent.

OKLCH solves this. It's a perceptually uniform color space, meaning equal numerical differences correspond to equal perceived differences. It separates color into three independent axes:

  • L (Lightness) — how bright or dark a color appears, from 0% (black) to 100% (white)
  • C (Chroma) — how vivid or saturated a color is, from 0 (gray) upward
  • H (Hue) — the color itself, as a degree on the color wheel (0–360)

Because these axes are independent, you can change a color's hue without affecting its perceived brightness, or adjust brightness without shifting the hue.

This matters for Daybridge because users can pick any color for their calendar events. With OKLCH, a red event and a blue event at the same shade will look equally prominent — same brightness, same vibrancy, just a different color. No event accidentally stands out or fades into the background because of its hue.

Luminance

The first step in building a color palette is choosing a set of lightness values — the L axis. We call these luminance stops, and they form the backbone of every color in the system. Whether a color is gray, purple, or green, it uses the same set of luminance values. This guarantees visual consistency across the entire palette.

Why not just space them evenly?

In a UI, most of the visual real estate lives at the extremes. In light mode, backgrounds, cards, and surfaces are all very light — they need to be subtly different from each other, but they're all clustered near white. In dark mode, the same is true near black. The middle of the range handles text and accents, where you don't need as many fine distinctions.

If you space luminance stops evenly, you waste stops in the middle where you don't need them and don't have enough at the extremes where you do. Instead, we compress stops at both ends of the scale so there are many subtle shades near white and near black, with a more relaxed, linear spacing through the mid-range.

How we generate the curve

We split the luminance range into three zones:

  • Light zone — shades near white, compressed together using a gamma curve so that light mode has plenty of subtle surface variations
  • Mid zone — shades in the middle, spaced linearly
  • Dark zone — shades near black, compressed together so that dark mode has the same level of surface subtlety

Both light and dark mode share this single palette. Light mode's surfaces draw from the top of the scale, dark mode's surfaces draw from the bottom, and the mid-range serves both modes for text, accents, and decorative elements.

Note that we don't extend all the way to pure black (0%). At the dark end, going too close to black makes the UI feel oppressively dark — shades become hard to distinguish and text becomes difficult to read.

Daybridge luminance values

02550751001815
123456789101112131415
100.099.999.096.892.385.076.067.058.049.040.030.021.915.913.0

Chroma

With luminance values chosen, the next question is: how vivid should each shade be?

In OKLCH, chroma controls saturation — a chroma of 0 is a neutral gray, and higher values are more vivid. But there's a catch: not every combination of lightness, chroma, and hue can be displayed on a screen. Very light and very dark colors can only support low chroma before they fall outside the screen's color gamut. And at any given lightness, some hues can go much more vivid than others — a bright yellow can be far more saturated than a bright blue at the same lightness.

Uniform chroma

We want every hue to look equally vivid at a given shade. To achieve this, we compute the uniform chroma for each luminance stop — the highest chroma that stays within the Display P3 gamut for every hue at that lightness. We find this by testing all 360° of hue and taking the lowest maximum.

The result is a per-shade chroma value that's safe for any hue. Shades near the extremes (very light or very dark) have low chroma because the gamut narrows there. Mid-range shades can support more vibrancy. On devices that don't support P3, the browser maps these colors to the nearest sRGB equivalent.

Daybridge chroma values

123456789101112131415
Shade
Chroma0.0000.0000.0050.0170.0400.0810.1330.1530.1320.1120.0910.0680.0500.0360.030
Example

Hue

With luminance and chroma defined, the final axis is hue — the actual color. Daybridge uses four color modes, each determining how the hue is applied.

Neutral

Most of the UI is neutral — backgrounds, surfaces, text, borders, and dividers. Neutral colors use near-zero chroma, making them essentially gray with only a very subtle tint. We've chosen a cool tint for our neutrals.

Grayscale hue
220°
Grayscale chroma
0.001
123456789101112131415

State

State colors communicate meaning — errors, warnings, success messages, and informational hints. Each has a fixed hue chosen for its conventional association.

At a uniform chroma (multiplier of 1.0), state colors can look a little washed out. But for states like error or warning, being noticeable is the whole point. So state colors apply a chroma multiplier that boosts vibrancy beyond the uniform safe limit. This makes them more vivid at the cost of some hues clipping to the gamut edge.

Hue
See table
Chroma multiplier
1.2x
StateHueUsage123456789101112131415
error30Destructive actions, validation failures
warning60Caution, potential issues
success140Confirmations, completed actions
info240Informational messages, tips

Brand

The Daybridge brand uses a single fixed hue for primary actions, active controls, and brand-level accents.

Brand hue
270°
Chroma multiplier
1x
123456789101112131415

Adaptive

Adaptive colors are the heart of the system. Unlike the other modes, the hue isn't fixed — it's chosen by the user. This is how calendar events get their color. Each event can be assigned any hue on the wheel, and the UI generates a full palette for that hue using the same luminance stops and chroma values as everything else.

This is where OKLCH's perceptual uniformity pays off. No matter which hue a user picks, their event will look consistent with every other event — same brightness, same vibrancy, just a different color. Use the hue control below to see how the palette adapts.

Hue
User-chosen
Chroma multiplier
1x
123456789101112131415

Semantic Colors

The luminance stops, chroma values, and hue modes described above are the raw building blocks. To actually use them in the UI, we define semantic colors — named tokens like surface, primary, high-contrast, or divider that describe a color's purpose rather than its literal value.

Each semantic color is composed of two things:

  • A shade — which of the luminance stops to use (1–15)
  • A transparency — an alpha value (0–1)

Crucially, a semantic color doesn't specify a hue or chroma. Those come from the color mode applied to the element. For example, the semantic color surface might be defined as shade 2 at full opacity. When rendered inside a neutral region, it resolves to a light gray. Inside a brand region, the same surface token resolves to a light purple. Inside an adaptive region with the user's chosen hue, it takes on that hue instead.

This separation is what makes the system composable — the same set of semantic definitions works across all four color modes without any mode-specific overrides.

Semantic colors are defined throughout the rest of these guidelines alongside the components and patterns that use them, such as surfaces, typography, controls, and inputs.