Files
agent-skills/skills/vue-best-practices/reference/sfc-scoped-css-slot-content.md
Jason Woltje f5792c40be feat: Complete fleet — 94 skills across 10+ domains
Pulled ALL skills from 15 source repositories:
- anthropics/skills: 16 (docs, design, MCP, testing)
- obra/superpowers: 14 (TDD, debugging, agents, planning)
- coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth)
- better-auth/skills: 5 (auth patterns)
- vercel-labs/agent-skills: 5 (React, design, Vercel)
- antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo)
- Plus 13 individual skills from various repos

Mosaic Stack is not limited to coding — the Orchestrator and
subagents serve coding, business, design, marketing, writing,
logistics, analysis, and more.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:27:42 -06:00

243 lines
4.6 KiB
Markdown

---
title: Scoped CSS Cannot Style Slot Content Directly
impact: HIGH
impactDescription: Slot content receives the parent component's scope, not the child's, causing styles to fail unexpectedly
type: gotcha
tags: [vue3, sfc, scoped-css, slots, deep-selector]
---
# Scoped CSS Cannot Style Slot Content Directly
**Impact: HIGH** - When a parent passes content through a slot, that content receives the parent component's scoped style attributes, not the child component's. This means the child component cannot style slot content with regular scoped CSS.
## Task Checklist
- [ ] Use `:deep()` selector in the wrapper component to style slot content
- [ ] Alternatively, use `:slotted()` pseudo-selector to target slotted elements
- [ ] For complex slot styling, consider using CSS modules or unscoped styles
- [ ] Document expected slot content structure when styling assumptions exist
**Problematic Code:**
```vue
<!-- Card.vue (child component) -->
<template>
<div class="card">
<div class="card-body">
<slot />
</div>
</div>
</template>
<style scoped>
.card-body {
padding: 1rem;
}
/* BAD: Won't apply to slot content! */
.card-body h2 {
color: #333;
margin-bottom: 0.5rem;
}
.card-body p {
color: #666;
}
</style>
```
```vue
<!-- Parent.vue -->
<template>
<Card>
<!-- This h2 and p won't be styled by Card's scoped CSS -->
<h2>Card Title</h2>
<p>Card description text.</p>
</Card>
</template>
```
**Correct Code:**
```vue
<!-- Card.vue - Using :slotted() -->
<template>
<div class="card">
<div class="card-body">
<slot />
</div>
</div>
</template>
<style scoped>
.card-body {
padding: 1rem;
}
/* GOOD: :slotted() targets slot content */
:slotted(h2) {
color: #333;
margin-bottom: 0.5rem;
}
:slotted(p) {
color: #666;
}
</style>
```
## Using :deep() Alternative
```vue
<!-- Card.vue - Using :deep() -->
<style scoped>
.card-body {
padding: 1rem;
}
/* :deep() also works for slot content */
.card-body :deep(h2) {
color: #333;
}
.card-body :deep(p) {
color: #666;
}
</style>
```
## Why This Happens
Slot content is compiled in the parent component's scope:
```vue
<!-- Parent template compiles to: -->
<Card>
<h2 data-v-parent123>Card Title</h2>
<p data-v-parent123>Card description</p>
</Card>
<!-- Card template compiles to: -->
<div class="card" data-v-card456>
<div class="card-body" data-v-card456>
<slot /> <!-- Content inserted WITHOUT data-v-card456 -->
</div>
</div>
```
The `<h2>` has `data-v-parent123`, but Card's scoped CSS expects `data-v-card456`.
## :slotted() vs :deep() for Slots
Both work, but have subtle differences:
```vue
<style scoped>
/* :slotted() - Specifically for slot content */
/* Only targets direct slotted elements */
:slotted(h2) {
color: blue;
}
/* :deep() - More general deep selector */
/* Can target nested elements within slot content */
.card-body :deep(h2) {
color: blue;
}
/* For nested elements in slot content, must use :deep() */
:slotted(.wrapper h2) { } /* Won't work for nested h2 */
.card-body :deep(.wrapper h2) { } /* Works for nested */
</style>
```
## Combining with Named Slots
```vue
<template>
<div class="card">
<header class="card-header">
<slot name="header" />
</header>
<div class="card-body">
<slot />
</div>
<footer class="card-footer">
<slot name="footer" />
</footer>
</div>
</template>
<style scoped>
/* Style specific slot content */
.card-header :slotted(h1),
.card-header :slotted(h2) {
margin: 0;
font-size: 1.25rem;
}
.card-body :slotted(p) {
margin-bottom: 1rem;
}
.card-footer :slotted(button) {
margin-right: 0.5rem;
}
</style>
```
## Performance Tip: Use Classes
Element selectors with `:slotted()` can be slower:
```vue
<style scoped>
/* SLOWER: Element selector */
:slotted(p) {
color: gray;
}
/* FASTER: Class selector */
:slotted(.card-text) {
color: gray;
}
</style>
```
## When to Use Unscoped Styles
For complex slot styling, unscoped styles may be cleaner:
```vue
<template>
<article class="article-card">
<slot />
</article>
</template>
<style>
/* Unscoped with unique prefix for complex content styling */
.article-card h1,
.article-card h2,
.article-card h3 {
font-family: Georgia, serif;
line-height: 1.2;
}
.article-card p {
line-height: 1.6;
}
.article-card img {
max-width: 100%;
}
.article-card blockquote {
border-left: 3px solid #ccc;
padding-left: 1rem;
}
</style>
```
## Reference
- [Vue.js Scoped CSS - Slotted Selectors](https://vuejs.org/api/sfc-css-features.html#slotted-selectors)
- [Vue.js Deep Selectors](https://vuejs.org/api/sfc-css-features.html#deep-selectors)