Urgent.News

What's breaking now, across thousands of outlets.

Tech

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…

I found myself tangled in a perplexing dark mode bug that refused to make logical sense. I was developing WidgetForge, a plug-and-play AI chat widget compatible with any site, be it static HTML or Next.js. The theme had four options, all relying on a single shared JS core. Despite the simplicity, one element consistently malfunctioned in dark mode, and I couldn't pinpoint the issue. The structure:

Every chat message featured icons like voice notes and status indicators. I desired these to invert correctly in dark mode, so I implemented the following CSS rule: @media (prefers-color-scheme: dark) { .voice-message-icon { filter: invert(1) brightness(2); } } I placed it within the appropriate style.css file. Upon testing, the fix worked perfectly when isolated.

However, once integrated into the actual widget, dark mode failed to take effect. Identical class name, same media query, same browser – no console errors, no typos. The problem was elusive, frustrating. After a moment of reflection, I realized the root of the issue:

The icon in question wasn't rendered from static HTML at all. Instead, it was dynamically generated at runtime via JavaScript, specifically when a voice message was 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 my styles were generated dynamically through JavaScript, they should be treated as separate stylesheets.

Regardless of how well-organized the main CSS file was, any dynamically injected block would override it in the cascade. Having identified this pattern, I discovered two more instances in the same codebase exhibiting the same behavior – runtime-generated elements with their own injected styles that had deviated from the main theme.

Recognizing this, I resolved the issue by incorporating state-dependent rules into the block authoritative for that element. While seemingly trivial, this insight significantly altered my understanding of where styling logic should reside when JavaScript dynamically generates markup. WidgetForge, a self-hosted AI chat widget, operates with static HTML or Next.js, four themes, and no database or build step. Live demo available.

Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.

Read the original at dev.to →

More in Tech

Dynamic Programming: The Matrix of Patterns

The Quest Begins (The "Why") I still remember the first time I saw a dynamic programming question pop up on a whiteboard during an interview.

  • Author recalls DP interview question with O(n²) brute force approach
  • Realization leads to understanding of linear-time DP solution via decisions at each index
  • Kadane's algorithm achieves O(n) time and O(1) space complexity for maximum subarray sum

I Built an Open-Source AWS Cloud Security tool for solo devs & founders !

If you're a solo dev running your own AWS account with no security team, you've probably had this thought: is my IAM setup actually fine, or did I just get lucky?

  • Plexavo is an open-source AWS security tool for solo developers
  • Scans IAM, S3, EC2, and networking configurations for misconfigurations
  • Provides 0-100 security score and actionable remediation instructions

More from Sunday 23 August →