Technology Aug 23, 2026 · 3 min read

The CSS bug that taught me JS-injected styles always win

I spent way longer than I'd like to admit chasing a dark mode bug that made zero sense on paper. I'm building WidgetForge, a drop-in AI chat widget you can paste into any site — static HTML or Next.js, pick a theme, done. Four themes, one shared JS core. Nothing exotic. Except one small piece of i...

DE
DEV Community
by Nogan's Development
The CSS bug that taught me JS-injected styles always win

I spent way longer than I'd like to admit chasing a dark mode bug that made zero sense on paper.

I'm building WidgetForge, a drop-in AI chat widget you can paste into any site — static HTML or Next.js, pick a theme, done. Four themes, one shared JS core. Nothing exotic.

Except one small piece of it kept breaking dark mode, and I couldn't figure out why.

The setup

Every message in the chat has little icons — a voice note badge, status icons, that kind of thing. I wanted those to invert properly in dark mode, so I wrote the obvious CSS:

@media (prefers-color-scheme: dark) {
  .voice-message-icon {
    filter: invert(1) brightness(2);
  }
}

Dropped it in the theme's style.css. Tested it. Worked fine in isolation.

Then I wired it into the actual widget and dark mode just... didn't apply. Same class name, same media query, same browser. No console errors. No typos I could find. It was the kind of bug where everything looks correct, which is the most annoying kind.

Where it actually was

Here's the thing I didn't clock at first: this specific icon isn't rendered from the static HTML at all. It's built at runtime, in JS, when a voice message gets added to the chat:

(function injectVoiceMessageStyles() {
  if (document.getElementById("voice-message-badge-styles")) {
    return;
  }

  const style = document.createElement("style");
  style.id = "voice-message-badge-styles";

  style.textContent = `
    .voice-message-icon {
      width: 16px;
      height: 16px;
      object-fit: contain;
      flex-shrink: 0;
    }
  `;

  document.head.appendChild(style);
})();

If your styles are partly generated by JS at runtime — dynamically injected <style> tags, CSS-in-JS, whatever — treat that as its own independent stylesheet. It doesn't matter how well-organized your "real" CSS file is if a script is appending a competing block after it loads. External stylesheets are static and load once; JS-injected blocks can show up whenever, and they'll happily override anything sitting above them in the cascade.

Once I knew to look for this pattern, I found two more spots in the same codebase doing the same thing — dynamically created UI elements with their own injected styles that had quietly drifted out of sync with the main theme file. Same fix each time: move the state-dependent rules into the block that's actually authoritative for that element.

Small bug, but it changed how I think about where styling logic should live once JS starts generating markup at runtime.

WidgetForge is a self-hosted AI chat widget — static HTML or Next.js, four themes, no database, no build step.

Live demo
Get the full template

DE
Source

This article was originally published by DEV Community and written by Nogan's Development.

Read original article on DEV Community
Back to Discover

Reading List