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

4.6 KiB

title, impact, impactDescription, type, tags
title impact impactDescription type tags
Scoped CSS Cannot Style Slot Content Directly HIGH Slot content receives the parent component's scope, not the child's, causing styles to fail unexpectedly gotcha
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:

<!-- 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>
<!-- 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:

<!-- 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

<!-- 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:

<!-- 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:

<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

<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:

<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:

<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