Ad – 728×90
🔧 Advanced CSS

CSS Best Practices – Writing Clean, Maintainable CSS

Writing CSS that works is straightforward. Writing CSS that stays maintainable as a project grows — across teams, across years — requires intentional habits. This lesson covers the proven practices: BEM naming, organizing stylesheets logically, keeping specificity flat, writing accessible styles, using shorthands correctly, and setting up a custom-property-based design system that scales.

⏱️ 22 min read 🎯 Intermediate 📅 Updated 2026 👁️ Lesson 5 of 5

Naming Conventions – BEM

BEM (Block–Element–Modifier) is the most widely adopted CSS naming convention. It makes the relationship between HTML and CSS explicit, keeps specificity to a single class, and prevents naming collisions.

CSS – BEM naming
/* Block — standalone component */
.card { }

/* Element — part of the block, separated by __ */
.card__title { }
.card__image { }
.card__body  { }
.card__footer { }

/* Modifier — variation of block or element, separated by -- */
.card--featured { }        /* block modifier */
.card--dark     { }
.card__title--large { }    /* element modifier */

/* HTML usage */
/* <div class="card card--featured">
     <img class="card__image">
     <h2 class="card__title card__title--large">...</h2>
     <div class="card__body">...</div>
   </div> */

/* Every class is flat — all (0,1,0) specificity — easy to override */

Organizing Your Stylesheet

Group rules logically so any developer can find the code they need. A common approach is the ITCSS (Inverted Triangle CSS) order from generic to specific:

CSS – Stylesheet structure
/* 1. Settings — custom properties, design tokens */
:root {
  --color-primary: #1572B6;
  --spacing-md: 16px;
}

/* 2. Reset / Base — normalize browser defaults */
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; font-family: system-ui, sans-serif; }

/* 3. Typography — h1-h6, p, a, lists */
h1 { font-size: 2rem; line-height: 1.2; }

/* 4. Layout — page structure, grid, sidebar, header */
.page-wrapper { max-width: 1200px; margin: 0 auto; padding: 0 var(--spacing-md); }

/* 5. Components — buttons, cards, nav, forms */
.btn { }
.card { }

/* 6. Utilities — single-purpose helper classes */
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); }
.text-center { text-align: center !important; }
.mt-0 { margin-top: 0 !important; }

Keeping Specificity Flat

CSS – Specificity management
/* BAD — deeply nested, high specificity */
#app .sidebar nav ul li a:hover { color: red; }   /* (1,1,3) */

/* GOOD — single class */
.nav-link:hover { color: red; }   /* (0,2,0) */

/* BAD — ID selector in stylesheets */
#hero-title { font-size: 3rem; }

/* GOOD — use a class even on unique elements */
.hero-title { font-size: 3rem; }

/* BAD — overusing !important */
.btn { color: blue !important; }
.btn.primary { color: white !important; }

/* GOOD — appropriate specificity */
.btn          { color: blue; }
.btn-primary  { color: white; background: #1572B6; }

/* Use :where() for resets so they never block overrides */
:where(ul, ol) { list-style: none; padding: 0; margin: 0; }
Ad – 336×280

CSS Performance Tips

CSS – Performance
/* 1. Animate only transform and opacity — GPU compositor, no layout */
.btn:hover {
  transform: translateY(-2px);   /* GOOD */
  opacity: 0.9;                  /* GOOD */
}
/* Avoid animating width, height, top, left, margin, padding — triggers layout */

/* 2. Use will-change for elements you KNOW will animate */
.modal {
  will-change: transform, opacity;
}
/* Remove will-change after animation — it consumes GPU memory */

/* 3. Contain layout thrash with contain */
.widget {
  contain: layout;   /* tells browser: changes inside don't affect outside */
}

/* 4. Avoid universal selector in hot paths */
/* BAD */ * { box-sizing: border-box; }   /* fine in reset, not in component */
/* OK as a one-time reset */

/* 5. Use logical shorthand — fewer declarations */
.card {
  margin: 16px;           /* shorthand for all 4 sides */
  padding: 12px 20px;     /* block / inline */
  border: 1px solid #ccc; /* width style color */
  font: 500 1rem/1.6 system-ui, sans-serif; /* weight size/line-height family */
  background: #fff url('/img/bg.svg') no-repeat center / cover;
}

Accessibility in CSS

CSS – Accessibility
/* 1. Never use display:none or visibility:hidden for screen-reader content */
/* Use the sr-only pattern instead */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* 2. Always style :focus — never just remove the outline */
:focus-visible {
  outline: 2px solid #1572B6;
  outline-offset: 3px;
}
/* NOT: * { outline: none; }  ← breaks keyboard navigation */

/* 3. Respect user motion preference */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* 4. Ensure sufficient color contrast — WCAG AA requires 4.5:1 for normal text */
/* Check with: https://webaim.org/resources/contrastchecker/ */
body { color: #1a1a1a; background: #fff; }   /* 17:1 — excellent */

/* 5. Respect user font size — use rem, not px for text */
html { font-size: 100%; }   /* respects browser default (16px) */
p    { font-size: 1rem; }   /* scales with user preferences */
h1   { font-size: 2rem; }

Design Token System

CSS – Full design token setup
:root {
  /* Colors */
  --color-primary:    #1572B6;
  --color-secondary:  #e44d26;
  --color-text:       #1a1a1a;
  --color-text-muted: #666;
  --color-bg:         #ffffff;
  --color-surface:    #f5f5f5;
  --color-border:     #e0e0e0;

  /* Spacing scale */
  --space-1:  4px;
  --space-2:  8px;
  --space-3:  12px;
  --space-4:  16px;
  --space-6:  24px;
  --space-8:  32px;
  --space-12: 48px;
  --space-16: 64px;

  /* Typography */
  --font-sans:  system-ui, -apple-system, Segoe UI, sans-serif;
  --font-mono:  'Fira Code', 'Cascadia Code', Consolas, monospace;
  --text-sm:    0.875rem;
  --text-base:  1rem;
  --text-lg:    1.125rem;
  --text-xl:    1.25rem;
  --text-2xl:   1.5rem;
  --text-3xl:   2rem;
  --leading:    1.7;

  /* Radii */
  --radius-sm:   4px;
  --radius-md:   8px;
  --radius-lg:   16px;
  --radius-full: 9999px;

  /* Shadows */
  --shadow-sm:  0 1px 3px rgba(0,0,0,.1);
  --shadow-md:  0 4px 12px rgba(0,0,0,.12);
  --shadow-lg:  0 8px 24px rgba(0,0,0,.15);

  /* Transitions */
  --ease:     cubic-bezier(.4,0,.2,1);
  --duration: 200ms;
}

📋 Summary

  • BEM naming: .block__element--modifier. Flat specificity, explicit relationships, no collisions.
  • Stylesheet order: Settings → Reset → Typography → Layout → Components → Utilities.
  • Specificity: avoid IDs in stylesheets, avoid !important outside utilities, use :where() for zero-specificity resets.
  • Performance: animate only transform and opacity. Use will-change sparingly. Prefer shorthand.
  • Accessibility: never remove :focus styling, use .sr-only for screen-reader content, respect prefers-reduced-motion, use rem for font sizes.
  • Design tokens: all raw values in :root custom properties. Components reference tokens, not magic numbers.
  • Shorthand: use margin, padding, border, font, background shorthand to reduce lines of CSS.

Frequently Asked Questions

Should I use BEM, SMACSS, or utility-first (Tailwind)? +

All three are valid and used widely in production. BEM is best for hand-written CSS on medium/large projects — it gives structure without tooling. SMACSS is similar but less prescriptive about naming. Utility-first (Tailwind) generates all utility classes at build time — maximum flexibility, minimal custom CSS, but larger HTML. The right choice depends on team size, project complexity, and whether you have a build step. For learning CSS itself, BEM with custom properties is the best foundation to understand the language before adding a framework.

How many stylesheets should a project have? +

For a small project, one well-organized stylesheet is fine. For larger projects, split into logical files: tokens.css, reset.css, typography.css, components/button.css, etc. With a build tool (Vite, Webpack), these get bundled into one HTTP request anyway. Without a build tool, use fewer files to minimize request overhead — the browser's cache will handle repeated visits efficiently.

When is it okay to use !important? +

Use !important only in two scenarios: (1) utility classes like .hidden { display: none !important } where the rule must always win and shouldn't be overridden by component CSS, and (2) overriding third-party library CSS when you cannot modify the library's source and their selectors have high specificity. Never use !important to "fix" a specificity problem in your own code — solve the actual specificity issue instead.