#Dev Log#CareReady#Frontend#CSS#Dark Mode
Two Moods, No Framework: Theming a PWA and the Light-on-Light Bug
Two themes from one body:not(.light) override layer — and the light-on-light bug that hid the belongings list in dark mode. Why a global colour override just relocates the problem, and the rule that prevents it.
CareReady has two themes. Light is the default: soft pastel “sticker book” cards on a cream background, aimed at older family caregivers who find it easier to read. Dark is opt-in, and I recently reworked it into something more fun — a “night party” with glowing cards and a logo that lights up like a little moon.
We don’t use Tailwind’s dark: variant. The base utility classes are the dark look (bg-gray-900, text-gray-300), and light mode is a single override block gated on a class:
body.light .text-gray-300 { color: #1f2937 !important; }
body.light .bg-gray-800 { background: #e9edf2 !important; }
/* …and so on */
Toggle body.light, and the whole app flips. It’s low-tech and it works. But it hides a specific, repeatable bug, and I hit it twice before I understood the pattern.
The bug: light-on-light
The belongings list uses candy-colored cards — bg-pink-50, bg-sky-50, etc. Those are light pastels in both themes (I never overrode them for dark). The item text, though, is text-gray-300 — which the body.light block remaps to a dark color for light mode.
Follow the two states:
- Light mode: card is
bg-pink-50(light), text is remapped to dark → dark on light. Readable. - Dark mode: card is still
bg-pink-50(light), text istext-gray-300→ light gray. Light gray on light pink. Invisible.
The list was unreadable in dark mode, and the reason is subtle: the background and the text were following different theming rules. The card was theme-independent (always light); the text was theme-following (dark in light mode, light in dark mode). In dark mode they collided.
The same bug appeared a second time on packed items, whose rows turn a pale color (bg-teal-100) when assigned to a box, while the item name stayed text-gray-200. Light row, light text, dark mode. Gone again.
The fix, and the wrong fix
The wrong fix — the one I tried first — was to globally brighten the colored text in dark mode:
body:not(.light) .text-pink-600 { color: #f9a8d4 !important; }
That reads great on the dark cards… and immediately breaks every text-pink-600 that sits on a white surface (modal headings, a pale-pink button). Now it’s light pink on white. Light-on-light, third time. A blanket color override just moves the collision around.
The right fix is to make the background and text agree per theme:
- Give the cards a marker class and let them go dark-tinted in dark mode, keeping the per-category hue (a dark rose, a dark sky, a dark amber). Now the light text has a dark surface to sit on, and the color-coding survives.
- For packed rows that stay pale, make the text explicitly dark (
text-gray-800) — it’s on a light surface in both themes, so it should be dark in both. - Scope any “make it glow” brightening to the element that actually needs it (
.candy-card h2), not to a color class used all over the app.
The rule
Light-on-light (and dark-on-dark) happens when a background and its text are governed by different theme rules. Before you pick a text color, ask: does the surface under it follow the theme, or is it fixed? If they don’t move together, one of the two states will be unreadable — and it’s usually the one you’re not looking at while you build.
The payoff for getting it right: two genuinely different moods from one small override layer — daytime pastels and a nighttime party — without a theming framework, and without a flash of unreadable text.
Takeaway: theme the surface and the text with the same rule, or one theme will hide your content. A global color override doesn’t fix light-on-light; it relocates it.
This is part of building CareReady, a released web app at VEAI LAB. My broader technical-PM profile is at hire-veai.com.

Thanks for reading — built with care, for caregivers.