Why That p Tag Refused to Recolor
I hit a weird rendering bug recently while implementing theme switching (light ↔ dark) on this site. Most of the UI flipped colors smoothly, but the text inside some <p> tags would… hesitate. Like it didn’t get the memo. Sometimes it would flash white. Sometimes it would stay stuck in the old theme for a few frames. Very glitchy. Very annoying.
What made it more confusing: this didn’t happen uniformly across all text elements. It happened mostly with <p> tags, I am unsure if this happens for other tags but I am very sure p tags are the culprits
GPU Hacks
Like any self-respecting dev faced with flickering visuals, I checked on some random blogs, tried to do chatgpt but nothing worked, I then reached for will-change and transform: translateZ(0) — the classic GPU-acceleration tricks, I assumed due to the size of content in the p tag GPU acceleration might just work
p {
will-change: color;
transform: translateZ(0);
}
Did that help?
Not really. The flickering persisted. In fact, it sometimes got worse — the browser seemed to be fighting itself on when to repaint the element.
The dreaded RCA
The moment of clarity came when I popped open DevTools and carefully stepped through the DOM during a theme toggle. Here’s what I saw:
- The
<body>class was toggling correctly (darkwas being added or removed). - Tailwind’s dark mode styles were working for most elements.
- But the
<p>tag itself wasn’t being repainted reliably.
I used the DevTools rendering overlay and noticed that the paragraph didn’t always trigger a repaint on theme change. Even though it should have — its color was different between themes.
Here’s the best theory I could confirm through testing:
The browser had internally cached how to render that
<p>based on its previous style tree. Due to a combination ofdisplay: block, lack of layout change, and font anti-aliasing layers, it deferred repainting the paragraph when the parent theme changed — especially when usingprefers-color-scheme+ class-based toggling (like Tailwind).
So even though the intent was to repaint, the layout engine said, “eh, it looks the same structurally, skip it.”
The Fix (aka: The Dumb Hack That Works)
I tried GPU hacks. I tried isolating the component with contain: paint. I even tried backface-visibility: hidden. All of them were unstable or brittle.
What finally worked?
Wrapping the paragraph in a div :D
<div>
<p className="transition-colors text-black dark:text-white">...</p>
</div>
That’s it.
By giving the <p> tag a parent container, it basically nudged the browser to treat it as a fresh renderable surface. It always repainted on theme change after that — no flickering, no ghosting, no visual lag.
It’s likely that the browser promotes <div>s with changing children more aggressively than <p>s, which are often treated as layout-static. The wrapper forced a new composite layer.
Before (flickering):
<body class="dark">
<p class="text-black dark:text-white">I flicker!</p>
</body>
Rendered structure:
[ body.dark ]
└─ p — text may not repaint
After (stable):
<body class="dark">
<div>
<p class="text-black dark:text-white">I behave.</p>
</div>
</body>
Rendered structure:
[ body.dark ]
└─ div
└─ p — repaint triggered
Takeaway
I have no clue what the main take away is apart from “Sometimes, a plain <div> is all it takes”
If you’re seeing weird flickering on dark mode toggles, don’t overthink it. Wrap it.