Best Practices
Write maintainable, performant, and accessible CSS at scale using naming conventions, architecture patterns, and modern tooling strategies.
BEM Methodology
Block Element Modifier (BEM) provides a naming convention that makes CSS predictable. Blocks are standalone components (.card), elements are parts of blocks (.card__title), and modifiers are variations (.card--featured).
BEM reduces specificity conflicts because each class is unique. It also makes HTML structure self-documenting — reading class names tells you the component hierarchy.
- Use double underscore for elements, double dash for modifiers
- A block can exist without elements; elements cannot exist without a block
- Avoid nesting BEM selectors — .card .card__title adds unnecessary specificity
.card { }
.card__title { }
.card__body { }
.card__footer { }
.card--featured {
border-color: var(--color-primary);
}CSS Architecture at Scale
Large projects need structure. The ITCSS (Inverted Triangle CSS) model organizes styles from generic to specific: Settings, Tools, Generic, Elements, Objects, Components, Utilities. Each layer increases in specificity.
Component-scoped CSS — whether via CSS Modules, scoped styles, or shadow DOM — prevents styles from leaking between features. Choose one strategy and enforce it consistently.
/* 1. Settings — variables */ /* 2. Tools — mixins (if using preprocessor) */ /* 3. Generic — reset, box-sizing */ /* 4. Elements — bare HTML tags */ /* 5. Objects — layout patterns */ /* 6. Components — UI components */ /* 7. Utilities — single-purpose helpers */
Performance Optimization
Minimize render-blocking CSS by inlining critical above-the-fold styles and deferring the rest. Remove unused CSS with PurgeCSS or built-in tree-shaking in modern bundlers.
Avoid expensive selectors — universal selectors, deep descendant chains, and :not() with complex arguments slow matching. Keep selectors short and use classes for targeting.
- Split CSS by route for code-splitting in SPA frameworks
- Use content-visibility: auto for off-screen sections
- Audit with Lighthouse and Coverage tab in DevTools
/* Avoid */
div > ul > li > a { }
*:not(.excluded) { }
/* Prefer */
.nav-link { }Layer and Cascade Management
CSS @layer lets you define explicit cascade priority. Declare layer order once, then assign styles to layers. This replaces specificity hacks with intentional ordering.
A typical layer stack: @layer reset, base, components, utilities. Utilities always win without needing !important because layer order takes precedence over specificity within the same origin.
@layer reset, base, components, utilities;
@layer reset {
*, *::before, *::after { box-sizing: border-box; margin: 0; }
}
@layer utilities {
.sr-only { /* always wins over components */ }
}Documentation and Linting
Stylelint enforces consistent formatting, catches errors, and prevents anti-patterns. Configure rules for property order, naming conventions, and disallowed units.
Document your design tokens, spacing scale, and color palette in a living style guide. Tools like Storybook showcase components with their CSS API, making onboarding faster and reducing one-off style overrides.
/* stylelint.config.js */
module.exports = {
extends: ['stylelint-config-standard'],
rules: {
'selector-class-pattern': '^[a-z][a-z0-9]*(-[a-z0-9]+)*(__[a-z0-9]+(-[a-z0-9]+)*)?(--[a-z0-9]+(-[a-z0-9]+)*)?$',
},
};