# How DESIGN.md Handles Hex, RGB, Oklch, and Named Colors in Its CSS Parser

> Learn how DESIGN.md normalizes hex, RGB, Oklch, and named colors to sRGB, ensuring consistent accessibility and token validation in its CSS parser.

- Repository: [Google Labs Code/design.md](https://github.com/google-labs-code/design.md)
- Tags: how-to-guide
- Published: 2026-07-03

---

**DESIGN.md normalizes every CSS color format—from legacy hex codes to modern Oklch values—into a canonical sRGB representation using the `parseCssColor` function, enabling consistent accessibility linting and token validation across the design system.**

The DESIGN.md linter processes color values through a dedicated parser that supports the full CSS Color Module Level 4 specification. Located in [`packages/cli/src/linter/model/color-parser.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/model/color-parser.ts), this utility converts hex, RGB, Oklch, named colors, and functional notations into a standardized `ParsedColorResult` structure. This normalization allows downstream lint rules to analyze contrast ratios and color relationships using a unified interface regardless of the original input format.

## The Entry Point: `parseCssColor`

The parser exposes a single entry function that dispatches to specialized handlers based on the input string's format.

```ts
export function parseCssColor(colorStr: string, depth = 0): ParsedColorResult | null

```

The function first normalizes the input by trimming whitespace and converting to lowercase. It then rejects values that exceed the maximum recursion depth of 32, which prevents infinite loops when parsing nested `color-mix` expressions. Based on the leading characters, the parser routes the string to one of several format-specific handlers.

### Supported Color Format Dispatch

The parser recognizes color types through the following detection patterns:

| Detected form | Handler |
|---------------|---------|
| `#…` | Hex (`parseHex`) |
| Named string | Named-color lookup (`CSS_NAMED_COLORS`) |
| `rgb(…)`, `rgba(…)` | RGB / RGBA |
| `hsl(…)`, `hsla(…)` | HSL / HSLA |
| `hwb(…)` | HWB |
| `lab(…)` | CIE Lab |
| `lch(…)` | CIE Lch |
| `oklab(…)` | Oklab |
| `oklch(…)` | Oklch |
| `color‑mix(…)` | Recursive color blending |

If no pattern matches, the function returns `null`, signaling an invalid color value to the caller.

## Hex Value Handling

The `parseHex` implementation in [`packages/cli/src/linter/model/color-parser.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/model/color-parser.ts) supports all four CSS hex forms: `#RGB`, `#RGBA`, `#RRGGBB`, and `#RRGGBBAA`. Short forms expand to 6- or 8-digit hex before parsing.

The parser extracts red, green, blue, and optional alpha components using `parseInt(..., 16)`, then constructs the result object. For example, a 3-digit hex code expands to 6 digits by duplicating each character.

```ts
import { parseCssColor } from './color-parser';

const result = parseCssColor('#0f8');
console.log(result?.hex); // "#00ff88"

```

This expansion occurs in the `parseHex` function within lines 66-84 of the color parser source file.

## Named Color Support

The parser consults a static map called `CSS_NAMED_COLORS` that enumerates every CSS Level 4 named color. When the lower-cased input matches a key in this map, the parser reuses `parseHex` on the stored hex string.

```ts
const result = parseCssColor('rebeccapurple');
console.log(result?.hex); // "#663399"

```

The complete mapping—from standard colors like `red` to `transparent` (mapped to `#00000000`)—resides in lines 24-56 of [`color-parser.ts`](https://github.com/google-labs-code/design.md/blob/main/color-parser.ts).

## RGB and RGBA Functional Notations

Functional notations utilize a `tokenizeFunc` helper that separates the function name from its arguments while respecting nested parentheses and the optional `/ alpha` separator. For RGB and RGBA values, the `parsePercentOrNumber` helper converts arguments to 0-255 values, while `parseAlpha` handles the optional opacity channel.

```ts
const result = parseCssColor('rgb(100% 0% 0% / 0.5)');
/*
{
  hex: '#ff000080',  // 8-digit because alpha < 1
  r: 255,
  g: 0,
  b: 0,
  a: 0.5,
  luminance: …
}
*/

```

## Oklch and Oklab (Perceptual Color Spaces)

The parser handles modern perceptual color spaces through dedicated conversion functions. For **Oklch**, the parser normalizes the hue component using `parseHue` (accepting `deg`, `grad`, `rad`, `turn`, or bare numbers) before converting to sRGB via `oklchToRgb`.

```ts
const result = parseCssColor('oklch(0.7 0.15 120deg)');
/*
{
  hex: '#7ac57c',
  r: 122,
  g: 197,
  b: 124,
  luminance: …
}
*/

```

Similarly, **Oklab** values process through `oklabToRgb` to produce the canonical sRGB result.

## Additional Functional Notations

The parser supports several other CSS color functions through specific conversion pipelines:

- **HSL / HSLA**: Hue normalization via `parseHue`, saturation and lightness parsed as percentages (0-1 range), then converted through `hslToRgb`.
- **HWB**: Hue, whiteness, and blackness values convert via `hwbToRgb`.
- **Lab / Lch**: CIE Lab values process through `labToRgb`, while CIE Lch uses `lchToRgb`.

All functional branches terminate by calling `makeResult`, which constructs the final `ParsedColorResult` object with normalized hex, RGB components, and calculated luminance.

## Color-Mix for Recursive Blending

The `color-mix` notation enables recursive blending of two colors. The parser evaluates this by recursively calling `parseCssColor` on each constituent color, then blending them using premultiplied alpha while respecting optional weight percentages.

```ts
const result = parseCssColor('color-mix(in srgb, #ff0000 30%, oklch(0.5 0.2 240deg) 70%)');
/* Returns a blended sRGB color, e.g. hex "#c44d9b" */

```

This recursive evaluation respects the 32-level depth limit to prevent stack overflow.

## Normalized Result Structure

Every color format ultimately resolves to a `ParsedColorResult` interface defined in the model layer:

```ts
export interface ParsedColorResult {
  hex: string;       // lower-case hex, 6-digit (or 8-digit if alpha < 1)
  r: number;         // 0-255
  g: number;
  b: number;
  a?: number;        // 0-1 (omitted if 1)
  luminance: number; // WCAG relative luminance
}

```

The **luminance** field stores the WCAG relative luminance calculated from the sRGB values, enabling contrast ratio calculations without re-parsing the original color string.

## Integration with the Linter Model

The `ModelSpec` class in [`packages/cli/src/linter/model/spec.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/model/spec.ts) orchestrates the parser integration. When processing a DESIGN.md document, the model walker identifies tokens with type `'color'` and invokes `parseCssColor`. Valid colors populate the `DesignSystemState.colors` map and the flattened `symbolTable` for fast lookups.

The validation helper `isValidColor` in [`spec.ts`](https://github.com/google-labs-code/design.md/blob/main/spec.ts) (lines 66-71) simply forwards to `parseCssColor`, returning a boolean indicating whether the string represents a valid CSS color.

Lint rules access the resolved color objects directly—querying `color.hex`, [`color.r`](https://github.com/google-labs-code/design.md/blob/main/color.r), `color.g`, `color.b`, and `color.luminance`—to perform accessibility checks and token consistency validation.

## Summary

- **DESIGN.md** handles color formats through a centralized parser in [`packages/cli/src/linter/model/color-parser.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/model/color-parser.ts) that converts all inputs to canonical sRGB.
- The `parseCssColor` function supports hex (3, 4, 6, 8-digit), named colors, RGB/RGBA, HSL/HSLA, HWB, Lab, Lch, Oklab, Oklch, and `color-mix`.
- All colors normalize to a `ParsedColorResult` containing hex, RGB components, optional alpha, and WCAG luminance.
- The `ModelSpec` class integrates the parser into the linting pipeline, storing results in the design system state for downstream rules.

## Frequently Asked Questions

### What happens if DESIGN.md encounters an invalid color format?

The `parseCssColor` function returns `null` when the input string does not match any recognized color pattern or exceeds the recursion depth limit for `color-mix` expressions. The `isValidColor` wrapper in [`spec.ts`](https://github.com/google-labs-code/design.md/blob/main/spec.ts) converts this to a boolean false, allowing lint rules to flag invalid color tokens without throwing exceptions.

### Does the parser support alpha transparency?

Yes, the parser handles alpha channels across all supported formats. For hex codes, it processes 8-digit `#RRGGBBAA` and 4-digit `#RGBA` notations. For functional notations, it parses the optional `/ alpha` separator. When alpha is less than 1, the resulting `ParsedColorResult` includes an `a` property (0-1 range) and produces an 8-digit hex string.

### How does DESIGN.md handle perceptually uniform color spaces like Oklch?

The parser implements dedicated conversion functions `oklchToRgb` and `oklabToRgb` that transform these modern color spaces into sRGB values. Unlike legacy RGB, these spaces provide uniform lightness perception, and the parser normalizes hue angles (supporting `deg`, `grad`, `rad`, `turn` units) before conversion to ensure accurate results.

### Can I use CSS `color-mix()` expressions in DESIGN.md?

Yes, the parser supports `color-mix(in srgb, color1, color2)` syntax with optional percentage weights. It recursively evaluates the component colors using `parseCssColor` with a depth counter, blends them using premultiplied alpha, and returns a single `ParsedColorResult` representing the mixed color.