CSS Architecture & Best Practices
Why CSS architecture matters
On small projects, you can write CSS however you want. But as projects grow — hundreds of components, multiple developers, thousands of lines — unstructured CSS becomes a nightmare. Styles conflict, specificity wars break out, and nobody dares touch shared classes.
Good CSS architecture prevents this.
The global reset
Start every project with a consistent baseline:
CSS
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
-webkit-text-size-adjust: 100%;
}
body {
min-height: 100vh;
line-height: 1.6;
font-family: system-ui, -apple-system, sans-serif;
-webkit-font-smoothing: antialiased;
}
img, picture, video, canvas, svg {
display: block;
max-width: 100%;
}
input, button, textarea, select {
font: inherit;
}This eliminates browser inconsistencies and saves you from debugging why padding doesn't match across browsers.
Naming conventions: BEM
BEM (Block, Element, Modifier) is the most widely used naming convention:
CSS
/* Block */
.card { }
/* Element (part of the block) */
.card__title { }
.card__body { }
.card__footer { }
/* Modifier (variation of block or element) */
.card--featured { }
.card__title--large { }HTML
<div class="card card--featured">
<h2 class="card__title card__title--large">Title</h2>
<p class="card__body">Content</p>
</div>BEM keeps selectors flat (no nesting), avoids specificity issues, and makes relationships clear.
Design tokens with CSS variables
Create a systematic design system:
CSS
:root {
/* Colors */
--color-primary: #2563eb;
--color-primary-dark: #1d4ed8;
--color-success: #22c55e;
--color-danger: #dc2626;
--color-text: #1e293b;
--color-text-muted: #64748b;
--color-bg: #ffffff;
--color-border: #e2e8f0;
/* Typography */
--font-sans: system-ui, -apple-system, sans-serif;
--font-mono: 'JetBrains Mono', monospace;
/* Spacing scale */
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 0.75rem;
--space-4: 1rem;
--space-6: 1.5rem;
--space-8: 2rem;
--space-12: 3rem;
/* Sizing */
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-full: 999px;
--max-width: 1200px;
}Every component references these tokens instead of hardcoding values. Want to change the primary color? One line.
File organization
For larger projects, split CSS into logical files:
Code
styles/
_reset.css /* Global reset */
_tokens.css /* Design tokens (variables) */
_typography.css /* Base text styles */
_layout.css /* Grid, container, page layout */
_components.css /* Buttons, cards, forms, etc. */
_utilities.css /* Helper classes (.hidden, .text-center) */
main.css /* Imports all above */Import order matters — later files can override earlier ones:
CSS
/* main.css */
@import '_reset.css';
@import '_tokens.css';
@import '_typography.css';
@import '_layout.css';
@import '_components.css';
@import '_utilities.css';Utility classes
Small, single-purpose classes for common patterns:
CSS
.flex { display: flex; }
.flex-col { flex-direction: column; }
.items-center { align-items: center; }
.gap-2 { gap: var(--space-2); }
.gap-4 { gap: var(--space-4); }
.text-center { text-align: center; }
.text-muted { color: var(--color-text-muted); }
.hidden { display: none; }
.sr-only {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
}Utility classes are composable — combine them in HTML instead of writing new CSS for every layout variation.
Avoiding common mistakes
1. Over-nesting selectors
CSS
/* Bad — too specific, hard to override */
.page .content .sidebar .widget .title { }
/* Good — flat and predictable */
.widget__title { }2. Using !important
If you need
!important, your architecture has a problem. Fix the specificity issue instead.3. Magic numbers
CSS
/* Bad — what does 47px mean? */
.header { height: 47px; }
/* Good — intentional */
.header { height: var(--header-height, 3rem); }4. Styling by element type in components
CSS
/* Bad — breaks if you change the tag */
.card p { margin-bottom: 1rem; }
/* Good — explicit class */
.card__text { margin-bottom: 1rem; }Performance tips
- Avoid
@importin production — it creates waterfall requests. Use a build tool to bundle CSS. - Remove unused CSS — tools like PurgeCSS scan your HTML and remove unused rules.
- Minimize repaints — avoid animating
width,height,top,left. Usetransformandopacity. - Use
containfor complex components — tells the browser a component's layout is independent:
CSS
.card {
contain: layout style;
}Component naming conventions in practice
BEM is widely used, but some teams prefer PascalCase blocks matching framework components (
.Card, .Card-title) or a feature prefix (.dashboard-stats). Whatever system you choose, document it and enforce it in review — consistency matters more than the exact syntax. Elements describe structure inside a block; modifiers describe state or variants (--disabled, --active) without spawning new blocks for every tweak.Avoid deep selector chains that mirror DOM structure (
div div .title) because they break when markup refactors. In plain CSS projects, pair semantic HTML with predictable class names so anyone can prototype without guessing which ancestor chain applies. Co-locate component CSS with its template when your build tool supports it, or group imports by component file under styles/components/.Scaling architecture across teams
As teams grow, introduce linting (stylelint) for property order, banned
!important, and disallowed ID selectors for styling. Code review should ask whether a utility or token could replace a one-off magic number. Refactors are safer when specificity is flat and key pages have visual regression checks. Architecture is a habit — small disciplined choices on day one prevent painful rewrites when the stylesheet reaches thousands of lines and multiple authors.Key takeaway
Good CSS architecture is about consistency and scalability. Use a reset, define design tokens as variables, follow a naming convention (BEM), organize files logically, and create utility classes for common patterns. These practices prevent the "CSS spaghetti" that plagues most projects.