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

推荐订阅源

SecWiki News
SecWiki News
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
Visual Studio Blog
博客园 - 叶小钗
S
SegmentFault 最新的问题
IT之家
IT之家
大猫的无限游戏
大猫的无限游戏
博客园_首页
Apple Machine Learning Research
Apple Machine Learning Research
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
月光博客
月光博客
酷 壳 – CoolShell
酷 壳 – CoolShell
腾讯CDC
D
Darknet – Hacking Tools, Hacker News & Cyber Security
V
V2EX
阮一峰的网络日志
阮一峰的网络日志
L
Lohrmann on Cybersecurity
量子位
C
Cyber Attacks, Cyber Crime and Cyber Security
T
Tor Project blog
J
Java Code Geeks
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
博客园 - 三生石上(FineUI控件)
Attack and Defense Labs
Attack and Defense Labs
AI
AI
The Cloudflare Blog
T
Tailwind CSS Blog
S
Schneier on Security
爱范儿
爱范儿
PCI Perspectives
PCI Perspectives
Stack Overflow Blog
Stack Overflow Blog
S
Secure Thoughts
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
T
The Exploit Database - CXSecurity.com
博客园 - 【当耐特】
V2EX - 技术
V2EX - 技术
S
Securelist
P
Proofpoint News Feed
T
Threat Research - Cisco Blogs
Help Net Security
Help Net Security
C
Cisco Blogs
N
News and Events Feed by Topic
人人都是产品经理
人人都是产品经理
B
Blog RSS Feed
K
Kaspersky official blog
T
The Blog of Author Tim Ferriss
G
Google Developers Blog
S
Security Affairs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Simon Willison's Weblog
Simon Willison's Weblog

daverupert.com

Finding curated public domain images 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 Inverted themes with light-dark() 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
A workaround for hiding empty slots in Chromium
Dave Rupert · 2026-06-19 · via daverupert.com

Named slots are one of web components’ biggest superpowers ✨. Imagine a Button component with an optional icon; in Web Components we don’t need a separate Button and IconButton, a single Button component with <slot name="icon"> will do. Or a card component with a handful of predefined slots for image, title, description, price, metadata, etc. Not 100 different cards… one card.

Slots are a wonderful way to declaratively compose UI, but one shadow styling challenge that has vexed my team over the years is how to contextually hide a <slot> when there’s no slotted content.

The problem… ghost gaps

The most idiomatic way to check if a particular slot is in-use is to use :host(:has([slot="foo"])). But… In a weird twist to how web component browser-compat bugs tend to go, :host(:has([slot])) works in Safari and Firefox today but doesn’t work in Chromium!

Chromium not supporting :host(:has([slot])) means we can’t query an element’s Light DOM contents from the Shadow DOM. That interpretation of encapsulation makes sense (I guess) and in most situations it’s not a big problem; an empty slot is like an empty span and probably won’t impact rendering. But it becomes a problem when the empty slots appear in a flex or grid layout using gap because unless you hide the element and/or its wrapper, the gap will not collapse in Chromium, so you end up with a “ghost gap”.

Example: With slot="optional" content in Light DOM

Required content

Optional content - expected: show content with gap

<optional-slot-demo-broken>
  <div slot="required">Required content</div>
  <div slot="optional">Optional content - expected: show content with gap</div>
</optional-slot-demo-broken>

Example: Without slot="optional" content in Light DOM

Required content

<optional-slot-demo-broken>
  <div slot="required">Required content</div>
  <!-- expected: does not show extra whitespace -->
</optional-slot-demo-broken>
  • ✅ Works in Safari = wrapper div hidden, no gap
  • ✅ Works in Firefox = wrapper div hidden, no gap
  • ❌ Broken in Chromium = extra whitespace below slot="required"

The failed attempts…

We’ve tried a lot of fixes over the last couple years. Variations on :host(:has()), element-name:has(), !important, ::slotted([slot]) ~ [slot], ::slotted([slot]:empty), and all permutations of those. Copilot/Claude were certain these would work… but alas…

The fix… use @scope

This week my co-worker Jeff and I were navigating this issue again. Coincidentally, the ShopTalkShow Discord was discussing the same exact problem and cited the HasSlotController in WebAwesome (née Shoelace) as a working solution to this problem. I’d prefer to keep the solution in pure CSS, but was getting desperate.

While glancing through the code I noticed the use of :scope in L38-40 and while unrelated it sparked a curiosity to try to use :scope to juke Chromium’s shadow limitations. A handful of tries later and I had a working solution.

Here’s what I came up with:

:host {
  display: grid;
  gap: 1rem;
}

/* Hide optional slots by default */
.optional-slot {
  display: none;
}

/* Show in Safari/Firefox */
:host(:has([slot="optional"])) .optional-slot {
  display: block;
}

/* Fix for Chromium */
@scope {
  :scope:has([slot="optional"]) .optional-slot {
    display: block;
  }
}

Example: With slot="optional" content in Light DOM

Required content

Optional content - expected: show content with gap

<optional-slot-demo>
  <div slot="required">Required content</div>
  <div slot="optional">Optional content - expected: show content with gap</div>
</optional-slot-demo>

Example: Without slot="optional" content in Light DOM

Required content

<optional-slot-demo>
  <div slot="required">Required content</div>
  <!-- expected: does not show extra whitespace -->
</optional-slot-demo>

And the results…

  • ✅ Works in Safari = wrapper div hidden, no gap
  • ✅ Works in Firefox = wrapper div hidden, no gap
  • ✅ Works in Chrome = wrapper div hidden, no gap

The why…

I love a good CSS-trick, but even worse than a bug is not knowing why something works. My other teammate Zacky dug into the spec and confirmed this is a viable fix because as its defined @scope without a scope-root in the shadow styles allows :scope to match the shadow-host instead of the shadow-root… which is clutch. From the spec:

The :scope selector can match the featureless shadow host when that host is the scoping root element. (Issue 9025)

And…

@scope rule without scope-start scopes to the shadow host instead of the shadow root. (Issue 9178)

Thanks, Team CSSScopeRule, for enabling this workaround (checks notes) three years ago. There’s a wonderful bit of irony that I’m using @scope to break out of the Shadow DOM’s style containment and that’s part of why I love CSS.

I look forward to the day we can use :host(:has()) and :has-slotted() in all browsers, but in the meantime I’m happy to have stumbled upon a duplicative-yet-simple fix. Let me know if you end up using this and it works for you. I already know we have a handful of minor spacing bugs to patch up in our system.