When I added dark mode to MemoryStub, my first attempt was exactly what you'd expect: I defined a darkBackground color and a darkText color and applied them conditionally. It worked. It also looked terrible — flat, lifeless, and inconsistent across screens.
The issue was I was thinking about dark mode as an inversion of light mode. It's not. They're two separate visual languages that happen to show the same content.
The fix was to stop referencing colors directly in components and start referencing semantic tokens. Instead of color: '#1a1a2e', I use color: theme.textPrimary. Each theme defines what textPrimary actually is. The component itself never knows or cares what the theme is.
This sounds obvious on paper. In practice, it requires discipline — especially when you're moving fast and it's tempting to just hardcode a color that looks right for the current screen you're working on. Every hardcoded color is future debt.
In light mode, elevation is shown with shadows — dark areas below bright surfaces. In dark mode, there are no shadows to speak of. Dark surfaces on dark backgrounds need a different signal: lighter surface color. The higher the elevation, the lighter the surface.
I missed this entirely in v1 of MemoryStub. Cards looked flat because they were the same color as the background. The fix: each elevation level in dark mode gets a slightly lighter cardBg token.
Don't just test dark mode once. Test it on a screen you haven't looked at in a week. You'll immediately spot the components where you hardcoded a color and forgot about it.
Images and media don't adapt to themes. A bright white screenshot on a dark-theme screen creates harsh contrast that breaks the visual flow. I started applying a subtle brightness reduction (opacity: 0.9 + a very light dark overlay) to user-uploaded images in dark mode. It's a small change that makes the whole experience feel intentional.
Theme systems reward the time you put into them upfront. Bolting one on halfway through a project is miserable. If you're starting a new app and you know you want dual themes — design both at the same time, from the first screen.