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>
209 lines
4.4 KiB
Markdown
209 lines
4.4 KiB
Markdown
---
|
|
title: Use Class Selectors in Scoped CSS for Performance
|
|
impact: MEDIUM
|
|
impactDescription: Element selectors in scoped CSS are slower because browsers must check both the element type and data attribute
|
|
type: efficiency
|
|
tags: [vue3, sfc, scoped-css, performance, css-selectors]
|
|
---
|
|
|
|
# Use Class Selectors in Scoped CSS for Performance
|
|
|
|
**Impact: MEDIUM** - When Vue compiles scoped CSS, it adds attribute selectors to every rule. Element selectors combined with attribute selectors (e.g., `p[data-v-xxx]`) are significantly slower for browsers to match than class selectors with attributes (e.g., `.text[data-v-xxx]`).
|
|
|
|
## Task Checklist
|
|
|
|
- [ ] Prefer class selectors over element selectors in scoped styles
|
|
- [ ] Avoid deeply nested element selectors in scoped CSS
|
|
- [ ] Use BEM or similar naming conventions to ensure unique class names
|
|
- [ ] For heavy styling, consider CSS modules or utility-first approaches
|
|
|
|
**Problematic Code:**
|
|
```vue
|
|
<template>
|
|
<article>
|
|
<header>
|
|
<h1>{{ title }}</h1>
|
|
<p>{{ subtitle }}</p>
|
|
</header>
|
|
<section>
|
|
<p>{{ content }}</p>
|
|
<ul>
|
|
<li v-for="item in items" :key="item">{{ item }}</li>
|
|
</ul>
|
|
</section>
|
|
</article>
|
|
</template>
|
|
|
|
<style scoped>
|
|
/* BAD: Element selectors are slower when scoped */
|
|
article {
|
|
max-width: 800px;
|
|
}
|
|
|
|
header {
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
h1 {
|
|
font-size: 2rem;
|
|
}
|
|
|
|
p {
|
|
line-height: 1.6;
|
|
}
|
|
|
|
ul {
|
|
padding-left: 1.5rem;
|
|
}
|
|
|
|
li {
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
</style>
|
|
```
|
|
|
|
**Correct Code:**
|
|
```vue
|
|
<template>
|
|
<article class="article">
|
|
<header class="article-header">
|
|
<h1 class="article-title">{{ title }}</h1>
|
|
<p class="article-subtitle">{{ subtitle }}</p>
|
|
</header>
|
|
<section class="article-content">
|
|
<p class="article-text">{{ content }}</p>
|
|
<ul class="article-list">
|
|
<li v-for="item in items" :key="item" class="article-list-item">
|
|
{{ item }}
|
|
</li>
|
|
</ul>
|
|
</section>
|
|
</article>
|
|
</template>
|
|
|
|
<style scoped>
|
|
/* GOOD: Class selectors are faster */
|
|
.article {
|
|
max-width: 800px;
|
|
}
|
|
|
|
.article-header {
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.article-title {
|
|
font-size: 2rem;
|
|
}
|
|
|
|
.article-subtitle,
|
|
.article-text {
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.article-list {
|
|
padding-left: 1.5rem;
|
|
}
|
|
|
|
.article-list-item {
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
</style>
|
|
```
|
|
|
|
## How Scoped CSS Compiles
|
|
|
|
Vue transforms scoped CSS by adding data attributes:
|
|
|
|
```css
|
|
/* What you write */
|
|
p { color: red; }
|
|
.text { color: blue; }
|
|
|
|
/* What Vue generates */
|
|
p[data-v-7ba5bd90] { color: red; }
|
|
.text[data-v-7ba5bd90] { color: blue; }
|
|
```
|
|
|
|
Browser CSS matching for `p[data-v-xxx]`:
|
|
1. Find all `<p>` elements (element lookup)
|
|
2. Check each for the `data-v-xxx` attribute (attribute check)
|
|
|
|
Browser CSS matching for `.text[data-v-xxx]`:
|
|
1. Find all elements with class `text` (class lookup - optimized)
|
|
2. Check each for the `data-v-xxx` attribute
|
|
|
|
Class lookups are highly optimized in modern browsers, making option 2 faster.
|
|
|
|
## When Performance Matters
|
|
|
|
This optimization matters most when:
|
|
- Rendering large lists (100+ items)
|
|
- Complex component trees
|
|
- Animation-heavy interfaces
|
|
- Mobile devices with limited CPU
|
|
|
|
```vue
|
|
<template>
|
|
<!-- 1000 items - performance difference is noticeable -->
|
|
<ul class="list">
|
|
<li v-for="item in items" :key="item.id" class="list-item">
|
|
<span class="item-name">{{ item.name }}</span>
|
|
<span class="item-price">{{ item.price }}</span>
|
|
</li>
|
|
</ul>
|
|
</template>
|
|
|
|
<style scoped>
|
|
/* Fast selectors for large lists */
|
|
.list { ... }
|
|
.list-item { ... }
|
|
.item-name { ... }
|
|
.item-price { ... }
|
|
</style>
|
|
```
|
|
|
|
## Acceptable Element Selectors
|
|
|
|
For small components with few elements, element selectors are fine:
|
|
|
|
```vue
|
|
<template>
|
|
<button class="btn">
|
|
<span>{{ label }}</span>
|
|
</button>
|
|
</template>
|
|
|
|
<style scoped>
|
|
/* OK for small, simple components */
|
|
.btn {
|
|
padding: 0.5rem 1rem;
|
|
}
|
|
|
|
span {
|
|
font-weight: bold;
|
|
}
|
|
</style>
|
|
```
|
|
|
|
## CSS Modules Alternative
|
|
|
|
For performance-critical components, CSS modules avoid the attribute selector entirely:
|
|
|
|
```vue
|
|
<template>
|
|
<p :class="$style.text">Content</p>
|
|
</template>
|
|
|
|
<style module>
|
|
/* Generates unique class names without attribute selectors */
|
|
.text {
|
|
color: red;
|
|
}
|
|
/* Compiles to something like: .text_abc123 { color: red; } */
|
|
</style>
|
|
```
|
|
|
|
## Reference
|
|
- [Vue Loader Scoped CSS](https://vue-loader.vuejs.org/guide/scoped-css.html#child-component-root-elements)
|
|
- [CSS Selector Performance](https://csswizardry.com/2011/09/writing-efficient-css-selectors/)
|