Styling Slotted Content
The ::slotted(selector) pseudo-element lets you write CSS rules inside the shadow root that target light-DOM nodes projected into a <slot>. When a consumer places a <p> or <strong> inside your custom element, those nodes remain in the light DOM but are visually rendered inside the shadow tree — ::slotted() is how you style them from within.
::slotted(p) { color: steelblue; }::slotted(strong) { color: #e11d48; }The rules above live inside the component’s shadow <style> block. Any <p> or <strong> element that the consumer slots into the component will receive those styles.
Limitations
Section titled “Limitations”::slotted() can only match top-level slotted nodes — the immediate children of the host that land in the slot. It cannot reach into the subtree of a slotted element:
::slotted(div span)does not work — you cannot target a<span>nested inside a slotted<div>.- Only compound selectors with a single slotted element are valid.
- Specificity of
::slotted()is low — equivalent to(0,0,1,0)— so page styles on the same element can win.
If you need deeper control over slotted subtrees, restructure your HTML to slot the innermost elements directly, or pass CSS custom properties into the shadow root from the page.
Trade-offs
Section titled “Trade-offs”| Option | Benefit | Cost |
|---|---|---|
::slotted() from the shadow root | The component author can set default styles for slotted content, so consumers don’t have to write any CSS themselves | Only matches top-level slotted nodes directly — can’t reach into a subtree, and low specificity means page CSS overrides it easily |
| Let the page style light DOM itself / pass in custom properties | Consumers already have full control over styling their own content since it’s light DOM — no dependency on ::slotted() at all | The component author loses control over default appearance if no baseline is set — slotted content may look inconsistent with the component |
Common mistakes
Section titled “Common mistakes”- Expecting a descendant selector inside
::slotted()to work — writing::slotted(p) span {}expecting to style aspannested inside a slottedp. This syntax is invalid —::slotted()only matches the top-level slotted element itself, not its descendants. - Using a deep compound selector like
::slotted(div span)— expecting to target aspannested inside a slotteddiv, but the spec only allows matching a compound selector at the same level as the slotted node. - Forgetting
::slotted()has low specificity — equivalent to only(0,0,1,0), so a page style directly targeting the same element can silently win, with no!importantneeded.
💡 Real-world examples
Shoelace — a component like
<sl-card>uses::slotted()to set default margins on content the consumer slots in, in case they didn’t set their own style, accepting the limitation that it can only adjust top-level nodes.Adobe Spectrum (Spectrum Web Components) — chooses to pass CSS custom properties into some components instead of relying on deep
::slotted(), because the structure of slotted content is often too complex for a shallow selector to handle.