TLDR
- All Dialtone color tokens are now stored and output as OKLCH instead of hex/HSL.
- Per-channel breakout CSS variables (
--dt-color-*-h,-s,-l,-a,-hsl,-hsla) are removed. - Theme CSS bundle reduced by ~31% (385 KB minified) from removing ~3,200 CSS variable declarations.
- Use the HSL-to-OKLCH Migration Tool to automate consumer migration.
- LESS
~""string escaping around color functions is no longer needed.
Quick checklist
- Run the HSL-to-OKLCH migration tool on your project.
- Search your codebase for
--dt-color-*-h,-s,-l,-hsl,-hsla. These variables no longer exist. - Visually verify key surfaces, gradients, and brand colors after migration.
Why OKLCH?
OKLCH (oklch(L C H)) is a perceptually uniform color space where L = lightness (0–1), C = chroma (0+), and H = hue (0–360). It replaces hsl()/hsla() across Dialtone for three reasons:
Perceptual uniformity. Equal numeric changes produce equal visual changes. In HSL,
hsl(60, 100%, 50%)(yellow) andhsl(240, 100%, 50%)(blue) have the same "lightness" but vastly different perceived brightness. OKLCH corrects this, making lightness adjustments predictable across all hues.Better gradients. Gradients interpolated in OKLCH avoid the muddy grays that appear in HSL transitions between distant hues. Colors stay vivid across the gradient.
Modern CSS integration. OKLCH enables relative color syntax (
oklch(from var(--token) l c h / alpha)), which can decompose any color into its channels at render time. This eliminates the need for pre-computed breakout variables, directly enabling the bundle size reduction below.
Performance Impact
Faster CSS parsing at runtime
This looks to be the single most impactful performance benefit. Every CSS custom property declaration the browser loads must be:
- Parsed and validated during stylesheet processing
- Stored in the CSSOM (CSS Object Model) in memory
- Resolved during style recalculation on every DOM mutation, resize, and interaction
- Cascaded through the inheritance chain at every element
Custom properties are especially expensive in the cascade because they participate in inheritance at every node. Removing ~3,200 declarations per theme speeds up style recalculation on every interaction, not just the initial page load.
Smaller CSS bundles
Removing the per-channel breakout variables eliminated ~3,200 CSS custom property declarations per theme. These variables (-h, -s, -l, -a, -hsl, -hsla for each color token) were the single largest contributor to Dialtone's CSS bundle size.
The file that matters most is dialtone-default-theme.min.css, which every Dialpad product loads:
| File | Before | After | Reduction |
|---|---|---|---|
dialtone-default-theme.min.css | 1,238 KB | 853 KB | -31.1% |
dialtone-default-theme.min.css (gzip) | 132 KB | 95 KB | -27.7% |
tokens-dp-light.css | 370 KB | 105 KB | -71.5% |
tokens-base-light.css | 99 KB | 31 KB | -68.8% |
That's 385 KB fewer on the theme file that every user loads. The token CSS files, dominated by breakout declarations, saw 68–72% reductions. Products that load token files independently (theming scenarios) benefit the most.
Developer experience
Beyond performance, the migration simplifies how developers work with Dialtone colors:
- Predictable lightness adjustments. In HSL,
calc(lightness - 5%)darkens yellow and purple by different perceived amounts. OKLCH lightness is perceptually uniform, socalc(l - 0.05)produces a consistent visual shift across all hues. - One token, one variable. As always, favor using semantic tokens instead of channel use base colors or manipulating with HSL breakout variables.
- Clearer intent.
oklch(from var(--token) l c h / 0.5)reads as "this token at 50% opacity." Compare that tohsla(var(--token-h), var(--token-s), var(--token-l), 0.5). - No more LESS
~""escaping. LESS parseshsl()as a built-in function, which breaks when CSS custom properties are used inside it.oklch()passes through LESS untouched, eliminating an entire class of compilation bugs.
What Changed
For Consumers of Dialtone
If your code references any HSL channel breakout variables, it needs to be updated. Refer to the HSL to OKLCH Migration Tool for help. Here are some common patterns and their replacements:
Before: composite HSL var + alpha
color: hsla(var(--dt-color-foreground-primary-hsl) / 0.5);
After: relative color syntax
color: oklch(from var(--dt-color-foreground-primary) l c h / 0.5);
Before: separate H,S,L vars + alpha
color: hsla(
var(--dt-color-foreground-primary-h),
var(--dt-color-foreground-primary-s),
var(--dt-color-foreground-primary-l),
0.25
);
After: single token reference
color: oklch(from var(--dt-color-foreground-primary) l c h / 0.25);
Before: HSL var without alpha
color: hsl(var(--dt-color-foreground-primary-hsl));
After: direct token reference
color: var(--dt-color-foreground-primary);
Before: LESS ~"" escape
color: ~"hsla(var(--dt-color-black-900-hsl) / 0.5)";
After: no escaping necessary
color: oklch(from var(--dt-color-black-900) l c h / 0.5);
LESS required ~"" string escaping because it tried to parse hsl() as a built-in function. LESS does not recognize oklch(), so the wrapper is no longer necessary.
For Contributors to Dialtone
All internal Dialtone CSS has already been converted. When writing new styles:
- Use the base token directly —
var(--dt-color-foreground-primary), never channel suffixes. - For alpha — use relative color syntax:
oklch(from var(--token) l c h / 0.5). - For lightness adjustment —
oklch(from var(--token) calc(l - 0.05) c h). OKLCH lightness is 0–1 (not 0–100% like HSL), so-5%becomes-0.05. - For desaturation — set chroma to 0:
oklch(from var(--token) l 0 h). - For combined adjustments — combine freely:
oklch(from var(--token) calc(l - 0.05) c h / 0.5).
Adjusting Opacity
Dialtone's opacity utilities (d-fco*, d-bgo*, d-bco*, d-dco*) continue to work exactly as before. The underlying mechanism uses CSS relative color syntax with an alpha keyword fallback:
/* Generated utility — alpha keyword preserves the token's original opacity */
.d-fc-primary {
color: oklch(from var(--dt-color-foreground-primary) l c h / var(--fco, alpha));
}
/* Opacity utility overrides the alpha channel */
.d-fco50 { --fco: 50%; }
When no opacity utility is applied, var(--fco, alpha) falls back to the alpha keyword, preserving the source color's original alpha (e.g. 0.65 for muted colors). When an opacity utility like .d-fco50 is applied, it sets --fco to 50%, overriding the alpha channel.
For custom CSS, apply alpha using relative color syntax:
/* Hardcoded alpha */
background: oklch(from var(--dt-color-surface-primary) l c h / 0.1);
/* Dynamic alpha via CSS variable */
background: oklch(from var(--dt-color-surface-primary) l c h / var(--my-alpha, 1));
Adjusting Color Channels
Relative color syntax lets you adjust any channel of a color token — lightness, chroma (saturation), and hue:
/* Darken by reducing lightness */
color: oklch(from var(--dt-color-foreground-critical) calc(l - 0.05) c h);
/* Lighten */
color: oklch(from var(--dt-color-foreground-critical) calc(l + 0.1) c h);
/* Desaturate (remove chroma) */
color: oklch(from var(--dt-color-foreground-critical) l 0 h);
/* Shift hue */
color: oklch(from var(--dt-color-foreground-critical) l c calc(h + 30));
/* Combine: darken + reduce opacity */
color: oklch(from var(--dt-color-foreground-critical) calc(l - 0.05) c h / 0.5);
Migration Tool: HSL to OKLCH
dialtone-migration-helper includes an hsl-to-oklch config that automates ~90% of consumer HSL breakout usage conversions.
The tool migrates:
- Composite
-hsl/-hslavar patterns (with and without alpha) - Separate
-h,-s,-lchannel var patterns (comma and space syntax) calc()on lightness channel- Desaturation patterns (saturation set to 0)
- LESS
~""string escaping (stripped automatically)
Requires manual review:
- LESS compile-time variables (
@step0,@step1) — app-internal, outside Dialtone's scope - Hardcoded HSL values (
hsla(137, 100%, 27%, 0.05)) — should adopt Dialtone tokens calc()on lightness: OKLCH lightness is perceptually different from HSL, so visually verify adjusted values
Usage
npx dialtone-migration-helper --cwd ./src
# Select "hsl-to-oklch" from the config list
Apply All Changes
npx dialtone-migration-helper --cwd ./src --force
# Select "hsl-to-oklch" from the config list
File Types Processed
- Stylesheets: CSS, LESS, SCSS, SASS, Stylus
- Templates: HTML, Vue, Markdown
- Scripts: JavaScript, TypeScript, JSX, TSX