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.
/* 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:
/* 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
/* 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; }
CSS Performance Tips
/* 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
/* 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
: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
!importantoutside utilities, use:where()for zero-specificity resets. - Performance: animate only
transformandopacity. Usewill-changesparingly. Prefer shorthand. - Accessibility: never remove
:focusstyling, use.sr-onlyfor screen-reader content, respectprefers-reduced-motion, useremfor font sizes. - Design tokens: all raw values in
:rootcustom properties. Components reference tokens, not magic numbers. - Shorthand: use
margin,padding,border,font,backgroundshorthand to reduce lines of CSS.
Frequently Asked Questions
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.
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.
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.