惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
博客园 - Franky
V
V2EX
Last Week in AI
Last Week in AI
H
Help Net Security
J
Java Code Geeks
WordPress大学
WordPress大学
阮一峰的网络日志
阮一峰的网络日志
Hugging Face - Blog
Hugging Face - Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
A
About on SuperTechFans
月光博客
月光博客
腾讯CDC
小众软件
小众软件
罗磊的独立博客
D
Docker
V
Visual Studio Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
Spread Privacy
Spread Privacy
博客园 - 叶小钗
F
Full Disclosure
Recent Announcements
Recent Announcements
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
L
LangChain Blog
T
The Exploit Database - CXSecurity.com
宝玉的分享
宝玉的分享
美团技术团队
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
L
LINUX DO - 热门话题
博客园 - 三生石上(FineUI控件)
T
Tailwind CSS Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
S
Securelist
Latest news
Latest news
Project Zero
Project Zero
T
Threat Research - Cisco Blogs
NISL@THU
NISL@THU
K
Kaspersky official blog
O
OpenAI News
T
Tenable Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
Cyberwarzone
Cyberwarzone
Vercel News
Vercel News
有赞技术团队
有赞技术团队
P
Proofpoint News Feed
爱范儿
爱范儿
B
Blog RSS Feed
U
Unit 42

daverupert.com

Finding curated public domain images A workaround for hiding empty slots in Chromium Spoons by Sam Vibe Check №42 The duality of language models in the browser 10,000-watt GPU meet 40-watt lump of meat I don’t want a screenshot of your Claude conversation When moving fast, talking is the first thing to break Ozempic dreams Before I go: People like it when other people make things People are not friction Smaller and dumber Priority of idle hands Magic Words Write about the future you want I’m swearing off APIs entirely Waiting for the power to go out The best version of my site so far… Focus rings with nested contrast-color()? Interpolate contrast-color() to manipulate lightness Using your design system colors with contrast-color() Algorithmic hover states with contrast-color() Twenty Twenty-Five Vibe Check №41 One thing churches do well Grid Paper Inkwell Games
Inverted themes with light-dark()
Dave Rupert · 2026-04-07 · via daverupert.com

We rolled out adaptive light-dark() support on our design system themes and it’s been a delightful upgrade. Creating light and dark variable sets isn’t difficult, but delivery has trade-offs. Most apps that do this probably ship both sets of token values in a single stylesheet. That’s fine until you have multiple kilobytes of duplicate definitions. To get around the performance problems we built two separate stylesheets –which is also not great– but my coworker Zacky found a good trick with <link disabled> to make it tolerable. Ultimately, we wanted to offer a single stylesheet for our human (and agent) friends to control theming.

Having light-dark() makes it trivial to support dual color modes in a single stylesheet and doesn’t add too much weight (0.5kb gzip for ~500 variables). It also gives you the ability to switch themes mid-page.

:root {
	color-scheme: light dark;
	--bg-color: light-dark(white, black);
	--text-color: light-dark(black: white);
	/* ...etc */
}

/* Add hard-coded theme overrides */
[data-theme="light"] { color-scheme: light; }
[data-theme="dark"] { color-scheme: dark; }

Adding [data-theme="dark"] to the body, or any element with color tokens defined, will force that section to be dark mode. The difference here between hanging variable definitions off a class is that you can respect the user preference without using JS to toggle, light-dark() does all the work.

Adding dark tier on a light theme is dramatic. Adding light tier on a dark theme sure would stand out. But when yielding light and dark modes over to the browser… something changes. In that situation hardcoding a theme mode into HTML loses a bit of meaning because I’m not controlling the theme anymore, the user is. What I actually want is the theme to be “opposite” the current theme. After a handful of attempts I think I came across a solution that’s easy to understand, maintain, and I even wrote a polyfill for you.

Automatic inversion with [data-theme=“inverted]

See the Pen [data-theme="inverted"] by Dave Rupert (@davatron5000) on CodePen.

The goal was to make it so that when the browser switched from light/dark modes, themed elements would switch to their inverse theme. The trick I uncovered was setting the current theme as a CSS variable:

:root {
	color-scheme: light dark;
	
	/* Initialize the theme variables */
	--theme: light; 
	@media (prefers-color-scheme: dark) {
		--theme: dark;
	}
}

/* Have data-theme use the variable */
[data-theme] { color-scheme: var(--theme); }

/* Update hard-coded themes to use the variable */
[data-theme="light"] { --theme: light; }
[data-theme="dark"] { --theme: dark; }

The reason we use manage --theme variable instead of purely color-scheme is because we can pass that into a style() query. An that’s the magic that enables inverted themes:

/* Add style query magic ✨ */
@container style(--theme: light) {
	[data-theme="inverted"] {
		--theme: dark;
	}
}

@container style(--theme: dark) {
	[data-theme="inverted"] {
		--theme: light;
	}
}

Again, the nice thing here is that we’re not creating two different trees of variables or re-redefining variables inside [data-theme="inverted"], we’re relying on light-dark() and color-scheme to do the heavy lifting. We’re contextually updating the color-scheme and letting the browser negotiate the cascade.

I added a .funky card theme to the demo to give an idea of how far you might be able to push this tech. Roman Kamorov noted in his post on Querying the Color Scheme something Vadim Makeev said on a Russian podcast that while this inversion trick is neat, people who prefer dark-mode (e.g. for medical reasons) probably want dark mode and not to be flash-banged mid-page. That’s something to think about and I think I have some ideas about that but I’d love to see/hear yours.

Polyfilling browser support

At the time of writing this trick only works in Safari 18+ and Edge/Chrome 111+. Firefox is the outlier but the good news is container style queries is on the roadmap for Interop 2026 and behind a flag in nightly. That gives you two options:

  • Roll out a bespoke polyfill for [data-theme="inverted]
  • Do nothing. Wait it out.

Given that I know the experience will auto-upgrade for Firefox users at some point this year, I’m prone to wait it out per the rules of progressive enhancement. If it falls back to the current theme, I don’t think the world falls over.

However, you probably support browsers outside the latest version and what’s acceptable for a fallback depends on your company’s understanding of the eventual consistency of browsers. iOS devices version-locked at Safari 17.4 are of particular concern for me, so I wrote an inverted theme polyfill.

The CSS style-query-support detection is pretty simple, but quirky.

/* Style query support check */
body {
  --syle-query-support: 0;
}

@container style(--theme) {
  body { --syle-query-support: 1 }
}

There’s a new @supports at-rule() function that would be more idiomatic, but you can’t detect at-rule function support with at-rule() so we have to do this variable hack. Ideally we could do this purely in JS with CSS.supports(), but alas. Booleans in CSS, what could go wrong?

That brings us to the JavaScript part which is pretty simple as well but comes with one big potential tradeoff

/**
 * Polyfill for inverted themes using a `--theme` variable in a style query
 * ⚠️ The use of `getComputedStyle` can trigger layout and style recalcs
 */
(()=>{
  const isContainerStyleQueriesSupported = () => {
    const bodyElStyle = getComputedStyle(document.body);
    const hasStyleQueries = bodyElStyle.getPropertyValue("--syle-query-support");
    
    if(hasStyleQueries === "1") {
      return true;
    }
    return false;
  }
  
  if(!isContainerStyleQueriesSupported()) {
    const invertedThemes = document.querySelectorAll('[data-theme="inverted"]');
  
    invertedThemes.forEach((el) => {
      const elTheme = getComputedStyle(el).getPropertyValue("--theme");
      const invertedTheme = elTheme == "light" ? "dark" : "light";
      el.dataset.theme = invertedTheme;
    })
  }
})();

Because we need to get the computed value of --style-query-support, getComputedStyle is known to trigger style recalcs and layout reflows, effectively penalizing all users not just browsers that don’t support container style queries. I tested this out by putting the polyfill inside a setTimeout, turning on paint flashing, and checking the Performance Monitor panel and I didn’t see any recalcs or layout reflows, but your mileage may vary depending on your setup.

Anyways, happy inverting! Let me know if you do something cool with it or if you already figured this out 10 months ago and I didn’t see your blog post.