Migrate from HSL to OKLCH: Complete Guide
Step-by-step guide to migrating your existing HSL color palette to OKLCH. Learn conversion strategies, common pitfalls, and best practices for a smooth transition.
Migrate from HSL to OKLCH: Complete Guide
Transitioning from HSL to OKLCH doesn't have to be overwhelming. This guide provides a systematic approach to migrating your existing color system while maintaining visual consistency and improving your design's quality.
Why Migrate?
Before we dive into the "how," let's quickly recap the "why":
- Perceptual uniformity: Colors with the same lightness actually look equally bright
- Better accessibility: Easier to ensure WCAG contrast compliance
- Predictable color manipulation: Lightening and darkening produces expected results
- Future-proof: Access to Display P3 and other wide-gamut colors
Pre-Migration Checklist
✅ Document your current color system - List all color variables
✅ Check browser support - Ensure your target browsers support OKLCH
✅ Set up fallbacks - Plan for graceful degradation
✅ Test environment ready - Have a dev/staging environment for testing
Migration Strategies
Strategy 1: Progressive Enhancement (Recommended)
This approach adds OKLCH alongside existing HSL colors, allowing gradual migration:
:root {
/* Original HSL (fallback) */
--color-primary: hsl(220deg 80% 55%);
/* New OKLCH (enhancement) */
--color-primary: oklch(0.60 0.20 250);
/* Browsers that understand oklch() will use this */
}
Pros:
- ✅ Safe - won't break in older browsers
- ✅ Gradual - migrate at your own pace
- ✅ Testable - A/B compare old vs. new
Cons:
- ⚠️ Doubled color declarations
- ⚠️ Requires cleanup later
Strategy 2: Feature Detection with @supports
Use CSS feature detection for more control:
.element {
/* Fallback for browsers without oklch support */
background: hsl(220deg 80% 55%);
color: hsl(0deg 0% 100%);
}
@supports (color: oklch(0 0 0)) {
.element {
/* Modern OKLCH colors */
background: oklch(0.60 0.20 250);
color: oklch(0.98 0.00 0);
}
}
Pros:
- ✅ Clean separation
- ✅ Explicit browser targeting
- ✅ Easy to maintain
Cons:
- ⚠️ More verbose
- ⚠️ Harder to scan
Strategy 3: Complete Replacement (Advanced)
For greenfield projects or if you only target modern browsers:
:root {
/* All colors use OKLCH */
--color-primary: oklch(0.60 0.20 250);
--color-background: oklch(0.98 0.01 270);
--color-text: oklch(0.20 0.02 270);
}
Pros:
- ✅ Clean, modern codebase
- ✅ No duplication
- ✅ Full OKLCH benefits
Cons:
- ❌ No fallback for older browsers
- ❌ Requires browser support confidence
Step-by-Step Migration Process
Step 1: Inventory Your Colors
Create a spreadsheet or document listing all colors:
/* Before - HSL inventory */
--primary: hsl(220deg 80% 55%);
--secondary: hsl(160deg 70% 45%);
--accent: hsl(340deg 85% 60%);
--background: hsl(0deg 0% 98%);
--text: hsl(0deg 0% 15%);
Step 2: Convert HSL to OKLCH
You can use online converters or the color picker tool, but here's the general approach:
For saturated colors:
- HSL lightness 50% ≈ OKLCH lightness 0.55-0.65 (depends on hue)
- HSL saturation 100% ≈ OKLCH chroma 0.25-0.35 (depends on lightness and hue)
For neutral colors:
- HSL saturation 0% = OKLCH chroma 0
- Lightness translates more directly: HSL 50% ≈ OKLCH 0.50-0.54
/* After - OKLCH conversion */
--primary: oklch(0.60 0.20 250); /* was hsl(220deg 80% 55%) */
--secondary: oklch(0.55 0.18 165); /* was hsl(160deg 70% 45%) */
--accent: oklch(0.70 0.22 345); /* was hsl(340deg 85% 60%) */
--background: oklch(0.98 0.00 0); /* was hsl(0deg 0% 98%) */
--text: oklch(0.25 0.00 0); /* was hsl(0deg 0% 15%) */
Step 3: Test Visual Consistency
Compare the old and new colors side-by-side:
<div class="comparison">
<div style="--color: hsl(220deg 80% 55%)">
<div style="background: var(--color)">HSL Original</div>
</div>
<div style="--color: oklch(0.60 0.20 250)">
<div style="background: var(--color)">OKLCH Converted</div>
</div>
</div>
Adjust OKLCH values until they match visually.
Step 4: Rebuild Your Palette
Now's the perfect time to create a consistent palette using OKLCH:
:root {
/* Primary palette - consistent lightness steps */
--primary-50: oklch(0.97 0.05 250);
--primary-100: oklch(0.93 0.08 250);
--primary-200: oklch(0.85 0.12 250);
--primary-300: oklch(0.75 0.15 250);
--primary-400: oklch(0.65 0.18 250);
--primary-500: oklch(0.55 0.20 250); /* Base */
--primary-600: oklch(0.45 0.20 250);
--primary-700: oklch(0.35 0.18 250);
--primary-800: oklch(0.25 0.15 250);
--primary-900: oklch(0.15 0.10 250);
}
Notice how we use:
- Consistent 0.10 lightness steps
- Slightly decreasing chroma for very light/dark shades
- Same hue throughout
Step 5: Update Semantic Colors
:root {
/* Before - HSL */
--success: hsl(140deg 70% 45%);
--warning: hsl(40deg 90% 55%);
--error: hsl(5deg 80% 50%);
--info: hsl(210deg 75% 55%);
/* After - OKLCH */
--success: oklch(0.65 0.20 145);
--warning: oklch(0.75 0.18 85);
--error: oklch(0.58 0.22 25);
--info: oklch(0.62 0.19 250);
}
Step 6: Handle Dark Mode
OKLCH makes dark mode much easier:
:root {
/* Light mode */
--bg: oklch(0.98 0.01 270);
--surface: oklch(0.95 0.02 270);
--text: oklch(0.20 0.02 270);
--text-muted: oklch(0.50 0.03 270);
}
@media (prefers-color-scheme: dark) {
:root {
/* Dark mode - just flip lightness values! */
--bg: oklch(0.15 0.02 270);
--surface: oklch(0.22 0.03 270);
--text: oklch(0.90 0.02 270);
--text-muted: oklch(0.65 0.03 270);
}
}
Common Pitfalls and Solutions
Pitfall 1: Over-Saturation
/* ❌ Problem - chroma too high */
--color: oklch(0.50 0.35 250);
/* Results in out-of-gamut color, might clip */
/* ✅ Solution - reduce chroma */
--color: oklch(0.50 0.22 250);
/* Vibrant but displayable */
Pitfall 2: Forgetting Neutrals Need Zero Chroma
/* ❌ Problem - gray with chroma */
--gray: oklch(0.50 0.05 270);
/* Has a blue tint */
/* ✅ Solution - zero chroma for true gray */
--gray: oklch(0.50 0.00 0);
/* Pure neutral gray */
Pitfall 3: Not Testing Accessibility
/* ❌ Problem - assumed contrast */
.button {
background: oklch(0.60 0.20 250);
color: oklch(0.80 0.15 250);
/* Only 0.20 lightness difference - poor contrast */
}
/* ✅ Solution - ensure adequate lightness difference */
.button {
background: oklch(0.60 0.20 250);
color: oklch(0.98 0.05 250);
/* 0.38 lightness difference - good contrast */
}
Tools for Migration
1. Browser DevTools
Modern browsers show OKLCH values in the color picker:
- Inspect element
- Click color swatch
- Choose "OKLCH" from format dropdown
2. Online Converters
- OKLCH Color Picker - Our interactive tool
- oklch.com - Quick conversions
- colorjs.io - Programmatic conversions
3. Build Tools
For automated conversion:
// Using colorjs.io library
import Color from 'colorjs.io';
const hslColor = new Color('hsl(220deg 80% 55%)');
const oklchColor = hslColor.to('oklch');
console.log(oklchColor.toString()); // oklch(0.60 0.20 250)
Testing Your Migration
Visual Testing
Create a test page with all colors:
<!DOCTYPE html>
<html>
<head>
<style>
.color-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 1rem;
}
.color-swatch {
height: 100px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
}
</style>
</head>
<body>
<h1>Color System Test</h1>
<div class="color-grid">
<div class="color-swatch" style="background: var(--primary-500)">
Primary 500
</div>
<!-- Repeat for all colors -->
</div>
</body>
</html>
Contrast Testing
Use tools to verify accessibility:
- Chrome DevTools Contrast Checker
- WebAIM Contrast Checker
- Manually check lightness differences (≥0.40 for AA)
Cross-Browser Testing
Test in:
- Chrome/Edge (Chromium)
- Safari
- Firefox
- Mobile browsers
Maintenance Tips
1. Document Your System
/**
* Color System - OKLCH
*
* Naming convention: --{category}-{shade}
* - Lightness steps: 0.10 increments
* - Base shade (500) has optimal chroma
* - Lighter/darker shades have slightly reduced chroma
*
* Accessibility: Ensure 0.40+ lightness difference for text
*/
2. Create Utility Functions
/* Use calc() for programmatic adjustments */
:root {
--base-h: 250;
--base-c: 0.20;
--primary-500: oklch(0.55 var(--base-c) var(--base-h));
--primary-600: oklch(calc(0.55 - 0.10) var(--base-c) var(--base-h));
}
3. Keep a Migration Log
Document what you've changed and why:
- Date of change
- Old HSL value
- New OKLCH value
- Reason for specific adjustments
Conclusion
Migrating from HSL to OKLCH is a worthwhile investment that pays dividends in design consistency, accessibility, and maintainability. Start small, test thoroughly, and you'll wonder how you ever lived without perceptually uniform colors.
Next steps:
- Try our color picker tool to experiment with OKLCH
- Learn about Display P3 colors
- Build a design system with OKLCH