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

推荐订阅源

罗磊的独立博客
L
LangChain Blog
aimingoo的专栏
aimingoo的专栏
IT之家
IT之家
B
Blog
博客园_首页
博客园 - 司徒正美
有赞技术团队
有赞技术团队
博客园 - 聂微东
I
InfoQ
美团技术团队
GbyAI
GbyAI
阮一峰的网络日志
阮一峰的网络日志
H
Help Net Security
大猫的无限游戏
大猫的无限游戏
MyScale Blog
MyScale Blog
WordPress大学
WordPress大学
The GitHub Blog
The GitHub Blog
A
About on SuperTechFans
人人都是产品经理
人人都是产品经理
Microsoft Azure Blog
Microsoft Azure Blog
Engineering at Meta
Engineering at Meta
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
The Cloudflare Blog

daverupert.com

Introducing Microlighter Shadow-piercing local component variables Vibe Check №43 Before I go: Double your business with companies you like The new yet familiar CodePen 2.0 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
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.