Fixing Theme Switching Issues in Astro with OKLCH Colors

The Problem

After implementing a dark/light theme switcher in our Astro blog, we encountered a critical visibility issue in light theme. The “View All” button and several UI elements became nearly invisible due to poor color contrast. This affected user experience significantly, especially for users who prefer light themes.

Theme visibility issue

The specific problems were:

  • “View All” buttons had text that was nearly invisible against the button background
  • Sidebar labels (“Categories”, “Tags”) were too light to read
  • Theme toggle button icon was barely visible
  • Tag badges had poor contrast against their backgrounds

Root Cause Analysis

The issue stemmed from our CSS variable configuration using the OKLCH color space. Here’s what went wrong:

/* Original problematic configuration */
:root {
--on-primary: oklch(0.23 0.084 var(--hue)); /* Dark color */
--btn-regular-bg: oklch(0.23 0.084 var(--hue)); /* Same dark color! */
}

The --on-primary variable was being used for both:

  1. General text color (which should be dark in light theme)
  2. Button text color (which needed to be light against dark button backgrounds)

This dual usage created a conflict where fixing one use case broke the other.

The Solution

We implemented a comprehensive fix by separating concerns and creating dedicated CSS variables:

1. CSS Variable Refinement

:root {
/* Text Colors */
--primary: oklch(0.7 0.14 var(--hue));
--on-primary: oklch(0.23 0.084 var(--hue)); /* Keep dark for general text */
--primary-hover: oklch(0.75 0.14 var(--hue));
/* New dedicated variables */
--btn-text: oklch(0.98 0.008 var(--hue)); /* Light color for button text */
--text-muted: oklch(0.5 0.08 var(--hue)); /* Medium contrast for secondary text */
}

2. Button Style Updates

.btn-regular {
background: var(--btn-regular-bg);
color: var(--btn-text); /* Use dedicated button text color */
@apply px-4 py-2 rounded-lg font-medium transition-colors duration-200;
}

3. Component-Level Adjustments

We fine-tuned individual components for better contrast:

SidebarNav.astro
<style>
.nav-title {
@apply text-lg font-semibold mb-4 text-gray-700 dark:text-gray-200;
/* Changed from text-gray-800 to text-gray-700 for better visibility */
}
</style>
ThemeToggle.tsx
className = 'text-gray-600 dark:text-gray-400 ...'
// Changed from text-gray-500 to text-gray-600
<!-- TopicList.astro -->class:list={
[
// Added explicit text colors for non-selected topics
{
'bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300':
selectedTopic !== normalizedName,
},
]
}

Understanding OKLCH Color Space

OKLCH is a perceptually uniform color space that makes it easier to create consistent color systems. The format is:

oklch(lightness chroma hue)
  • Lightness (0-1): 0 is black, 1 is white
  • Chroma (0-0.4+): Color intensity/saturation
  • Hue (0-360): Color angle on the color wheel

Why OKLCH for Theme Switching?

  1. Predictable Lightness: Unlike HSL, OKLCH maintains perceptual lightness across different hues
  2. Consistent Contrast: Easier to maintain WCAG contrast ratios
  3. Smooth Transitions: Better color interpolation for animations

Lessons Learned

1. Separate Concerns in CSS Variables

Don’t use the same color variable for different UI contexts. Create specific variables for:

  • Button text vs body text
  • Interactive elements vs static content
  • Primary actions vs secondary information

2. Test Both Themes Thoroughly

Always test UI changes in both light and dark themes. What works in one theme might break in another.

3. Use Semantic Color Names

Instead of generic names like --text-color, use context-specific names:

  • --btn-text
  • --nav-label
  • --card-title

4. Maintain Contrast Ratios

Ensure all text meets WCAG AA standards:

  • Normal text: 4.5:1 contrast ratio
  • Large text: 3:1 contrast ratio
  • Use tools like WebAIM’s contrast checker

Implementation Checklist

When implementing theme switching, follow this checklist:

  • Define separate CSS variables for different text contexts
  • Test all interactive elements in both themes
  • Verify contrast ratios meet accessibility standards
  • Document color system decisions
  • Create visual regression tests for theme changes

Conclusion

Theme switching is more complex than just inverting colors. It requires a thoughtful approach to color system design, considering how each variable is used across different contexts. By separating concerns and using semantic variable names, we created a robust theme system that works beautifully in both light and dark modes.

The OKLCH color space proved invaluable for maintaining consistent perceptual lightness and contrast across our theme variations. This fix not only resolved our immediate visibility issues but also established a more maintainable color system for future development.

Resources