Adding dark mode to GOTRS exposed every hardcoded colour in our templates. Here’s what we learned.
The Problem
Our first attempt was simple: detect dark mode preference, swap some background colours. This worked until we noticed inputs still had white backgrounds. Badges used hardcoded colours. Alerts ignored the theme entirely. Form fields glowed like searchlights against dark backgrounds.
Dark mode with bright white form fields is not a good look. We had colours scattered across dozens of templates with no consistency.
Our Solution
We rebuilt styling around CSS custom properties (design tokens). Instead of background: #ffffff, components use background: var(--bg-card). One variable change, consistent theming everywhere.
:root {
--bg-card: #ffffff;
--text-primary: #212529;
}
[data-theme="dark"] {
--bg-card: #1f2937;
--text-primary: #f1f5f9;
}
But dark mode isn’t just inverting colours. We adjusted contrast ratios for readability, shadow colours (black shadows are invisible on dark backgrounds), border opacity, and focus indicators. Accessibility can’t be sacrificed for aesthetics.
The Benefits
The customer portal we built later inherited the same tokens without extra work. Adding the theme system was trivial - the design tokens were already in place.
Every badge, button, and card now uses the same token system. The codebase went from scattered colour values to a single source of truth.
Agents who work extended shifts no longer get blinded by their screens. That alone made the refactoring worthwhile.
The real lesson: design tokens should be the starting point, not an afterthought.