CSS Architecture & Best Practices
Organize, scale, and maintain CSS codebases with proven methodologies, naming conventions, performance techniques, and accessibility patterns.
BEM Methodology
BEM (Block Element Modifier) is the most widely used CSS naming convention. It creates clear, predictable class names that show the relationship between components.
/* Block: standalone component */ .card { } /* Element: part of a block (double underscore) */ .card__title { } .card__image { } .card__body { } .card__footer { } /* Modifier: variation of a block or element (double hyphen) */ .card--featured { } .card--compact { } .card__title--large { } .card__image--rounded { }
/* Block */ .card { border-radius: 12px; overflow: hidden; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.05); } /* Elements */ .card__image { width: 100%; height: 200px; object-fit: cover; } .card__body { padding: 1.25rem; } .card__title { font-size: 1.25rem; font-weight: 600; margin-bottom: 0.5rem; } .card__text { opacity: 0.7; line-height: 1.6; } .card__footer { padding: 1rem 1.25rem; border-top: 1px solid rgba(255,255,255,0.06); display: flex; justify-content: space-between; align-items: center; } /* Modifiers */ .card--featured { border-color: #6366f1; box-shadow: 0 0 20px rgba(99,102,241,0.15); } .card--compact .card__body { padding: 0.75rem; }
.card__title, not .card__body__title. If you need deeper nesting, it's time to create a new Block.
SMACSS Overview
SMACSS (Scalable and Modular Architecture for CSS) organizes styles into five categories.
/* 1. Base — element defaults (no classes) */ html { font-size: 16px; } a { color: #6366f1; } /* 2. Layout — major page sections (prefix: l-) */ .l-header { position: sticky; top: 0; } .l-sidebar { width: 280px; } .l-main { flex: 1; } /* 3. Module — reusable components */ .card { } .btn { } .modal { } /* 4. State — dynamic states (prefix: is-) */ .is-active { } .is-hidden { display: none; } .is-loading { opacity: 0.5; } /* 5. Theme — visual overrides */ .theme-dark { --bg: #0a0a0f; } .theme-light { --bg: #ffffff; }
OOCSS
Object-Oriented CSS separates structure from skin and container from content.
/* Separate STRUCTURE from SKIN */ /* Structure (shared layout) */ .media { display: flex; gap: 1rem; align-items: flex-start; } /* Skin (visual variation) */ .media--bordered { border: 1px solid #ccc; padding: 1rem; border-radius: 8px; } /* Separate CONTAINER from CONTENT */ /* Bad: */ .sidebar h3 { font-size: 14px; } /* Good: */ .section-title { font-size: 14px; }
Atomic / Utility-First CSS
Utility-first CSS (popularized by Tailwind CSS) uses single-purpose classes composed directly in HTML. Great for rapid development, controversial for maintainability.
/* Spacing utilities */ .m-0 { margin: 0; } .mt-4 { margin-top: 1rem; } .p-4 { padding: 1rem; } .px-6 { padding-inline: 1.5rem; } /* Display */ .flex { display: flex; } .grid { display: grid; } .hidden { display: none; } .block { display: block; } /* Typography */ .text-center { text-align: center; } .font-bold { font-weight: 700; } .text-sm { font-size: 0.875rem; } /* Colors */ .text-primary { color: var(--color-primary); } .bg-surface { background: var(--color-surface); } /* HTML usage: */ <div class="flex gap-4 p-6 bg-surface rounded-lg"> <h2 class="text-lg font-bold text-primary">Title</h2> <p class="text-sm mt-2">Description</p> </div>
Naming Conventions
/* BEM: block__element--modifier */ .search-form__input--focused { } /* camelCase (CSS Modules) */ .searchFormInput { } /* kebab-case (most common) */ .search-form-input { } /* Namespace prefixes */ .c-card { } /* c- = component */ .l-grid { } /* l- = layout */ .u-hidden { } /* u- = utility */ .is-active { } /* is- = state */ .js-toggle { } /* js- = JavaScript hook (never style these) */ .t-dark { } /* t- = theme */
File Organization
styles/
├── base/
│ ├── _reset.css /* CSS reset or normalize */
│ ├── _typography.css /* font-face, base font rules */
│ └── _base.css /* html, body, global element styles */
│
├── abstracts/
│ ├── _variables.css /* custom properties / design tokens */
│ └── _mixins.scss /* Sass mixins (if using preprocessor) */
│
├── layout/
│ ├── _header.css
│ ├── _footer.css
│ ├── _sidebar.css
│ └── _grid.css
│
├── components/
│ ├── _button.css
│ ├── _card.css
│ ├── _modal.css
│ ├── _form.css
│ └── _nav.css
│
├── utilities/
│ ├── _spacing.css
│ ├── _display.css
│ └── _text.css
│
├── pages/ /* page-specific overrides */
│ ├── _home.css
│ └── _about.css
│
├── vendors/ /* third-party CSS */
│ └── _normalize.css
│
└── main.css /* imports everything *//* Import order matters! From generic to specific. */ @import 'vendors/normalize.css'; @import 'abstracts/variables.css'; @import 'base/reset.css'; @import 'base/typography.css'; @import 'base/base.css'; @import 'layout/header.css'; @import 'layout/sidebar.css'; @import 'layout/footer.css'; @import 'components/button.css'; @import 'components/card.css'; @import 'components/modal.css'; @import 'utilities/spacing.css'; @import 'utilities/display.css';
CSS Reset vs Normalize
Resets strip all default browser styles. Normalize preserves useful defaults while fixing inconsistencies. Modern resets take a balanced approach.
/* Nuclear option: remove ALL defaults */ *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; } ul, ol { list-style: none; } a { text-decoration: none; color: inherit; } img { max-width: 100%; display: block; } button, input, select, textarea { font: inherit; }
/* Normalize: fix inconsistencies, keep useful defaults */ html { line-height: 1.15; -webkit-text-size-adjust: 100%; } main { display: block; } /* IE fix */ h1 { font-size: 2em; margin: 0.67em 0; /* consistent across browsers */ } b, strong { font-weight: bolder; } small { font-size: 80%; }
Modern Minimal Reset
A pragmatic, modern reset inspired by Josh Comeau and Andy Bell.
/* Modern Minimal CSS Reset */ /* 1. Use border-box everywhere */ *, *::before, *::after { box-sizing: border-box; } /* 2. Remove default margins */ * { margin: 0; } /* 3. Sensible body defaults */ body { line-height: 1.5; -webkit-font-smoothing: antialiased; } /* 4. Improve media defaults */ img, picture, video, canvas, svg { display: block; max-width: 100%; } /* 5. Inherit fonts for form controls */ input, button, textarea, select { font: inherit; } /* 6. Avoid text overflow */ p, h1, h2, h3, h4, h5, h6 { overflow-wrap: break-word; } /* 7. Root stacking context */ #root, #__next { isolation: isolate; } /* 8. Smooth scroll with motion preference */ @media (prefers-reduced-motion: no-preference) { html { scroll-behavior: smooth; } }
Specificity Management
/* Specificity: (inline, IDs, Classes, Elements) */ p /* (0, 0, 0, 1) */ .card /* (0, 0, 1, 0) */ .card .card__title /* (0, 0, 2, 0) — avoid this! */ #header /* (0, 1, 0, 0) — avoid IDs for styling */ style="..." /* (1, 0, 0, 0) — inline, very hard to override */ /* RULES FOR MANAGEABLE SPECIFICITY: */ /* 1. Never use IDs for styling */ #header { } /* Bad */ .header { } /* Good */ /* 2. Avoid nesting selectors more than 2 levels */ .nav .list .item a { } /* Bad: (0,0,3,1) */ .nav__link { } /* Good: (0,0,1,0) */ /* 3. Use :where() to remove specificity when needed */ :where(.card, .panel) .title { } /* (0,0,1,0) — :where() adds zero */ /* 4. Use @layer for specificity control (modern) */ @layer base, components, utilities;
Design Tokens with CSS Variables
Design tokens are the single source of truth for your design system. Use CSS custom properties for colors, spacing, typography, and more.
:root { /* --- Colors --- */ --color-primary: #6366f1; --color-primary-light: #818cf8; --color-primary-dark: #4f46e5; --color-secondary: #f472b6; --color-success: #34d399; --color-warning: #fbbf24; --color-error: #ef4444; /* --- Surfaces --- */ --bg-primary: #0a0a0f; --bg-surface: #12121a; --bg-elevated: #1e1e2e; --text-primary: #e2e8f0; --text-secondary: #94a3b8; /* --- Typography --- */ --font-sans: 'Inter', system-ui, sans-serif; --font-mono: 'Fira Code', monospace; --font-size-xs: 0.75rem; --font-size-sm: 0.875rem; --font-size-base: 1rem; --font-size-lg: 1.125rem; --font-size-xl: 1.25rem; /* --- Spacing Scale --- */ --space-1: 0.25rem; /* 4px */ --space-2: 0.5rem; /* 8px */ --space-3: 0.75rem; /* 12px */ --space-4: 1rem; /* 16px */ --space-6: 1.5rem; /* 24px */ --space-8: 2rem; /* 32px */ /* --- Border Radius --- */ --radius-sm: 4px; --radius-md: 8px; --radius-lg: 12px; --radius-full: 9999px; /* --- Shadows --- */ --shadow-sm: 0 1px 2px rgba(0,0,0,0.2); --shadow-md: 0 4px 12px rgba(0,0,0,0.3); --shadow-lg: 0 8px 24px rgba(0,0,0,0.4); /* --- Transitions --- */ --transition-fast: 150ms ease; --transition-base: 250ms ease; --transition-slow: 400ms ease; }
Preprocessors Overview
Sass and Less add features like nesting, variables, mixins, and functions to CSS. With modern CSS catching up (native nesting, custom properties), preprocessors are less critical but still useful.
// Variables $primary: #6366f1; $spacing: 1rem; // Nesting .card { padding: $spacing; &__title { font-weight: 600; } &--featured { border-color: $primary; } &:hover { transform: translateY(-2px); } } // Mixins @mixin respond-to($bp) { @media (min-width: $bp) { @content; } } .grid { grid-template-columns: 1fr; @include respond-to(768px) { grid-template-columns: repeat(2, 1fr); } } // Functions & loops @for $i from 1 through 5 { .mt-#{$i} { margin-top: $i * 0.25rem; } }
color-mix(), and @layer, the gap is closing fast. Consider native CSS for new projects — it's simpler, has no build step, and is debuggable in DevTools.
CSS Performance
/* 1. REMOVE UNUSED CSS */ /* Tools: PurgeCSS, UnCSS, Chrome Coverage tab */ /* Average site ships 80%+ unused CSS! */ /* 2. USE EFFICIENT SELECTORS */ /* Slow: universal and deeply nested */ div > ul > li > a > span { } /* Bad */ [class*="icon-"] { } /* Bad: substring match */ /* Fast: class-based, shallow */ .nav__link { } /* Good */ .icon-home { } /* Good */ /* 3. CRITICAL CSS — inline above-the-fold styles */ <head> <style> /* Critical: hero, nav, first section */ .hero { ... } .nav { ... } </style> <link rel="stylesheet" href="styles.css" media="print" onload="this.media='all'"> </head> /* 4. MINIMIZE LAYOUT THRASHING */ /* Avoid properties that trigger layout: */ /* width, height, top, left, margin, padding, font-size */ /* Prefer transform & opacity for animations */ .animate { will-change: transform; /* hint to browser */ transition: transform 0.3s; } /* 5. USE content-visibility FOR OFFSCREEN CONTENT */ .offscreen-section { content-visibility: auto; /* skip rendering until visible */ contain-intrinsic-size: auto 500px; }
CSS Accessibility
CSS plays a crucial role in accessibility: focus indicators, color contrast, screen reader utilities, and motion preferences.
/* NEVER do this: */ *:focus { outline: none; } /* Removes ALL focus indicators! */ /* DO this: custom focus styles */ :focus-visible { outline: 2px solid #6366f1; outline-offset: 2px; } /* :focus-visible = keyboard focus only, not mouse clicks */ button:focus-visible { outline: 2px solid #6366f1; outline-offset: 2px; box-shadow: 0 0 0 4px rgba(99,102,241,0.3); }
/* Visually hidden but accessible to screen readers */ .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border-width: 0; } /* HTML: <span class="sr-only">Open navigation menu</span> */
/* Respect user motion preferences */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; } } /* High contrast mode */ @media (prefers-contrast: high) { :root { --border-color: #fff; --text-secondary: #e2e8f0; /* brighter muted text */ } } /* WCAG Color Contrast Requirements: Normal text: 4.5:1 minimum (AA), 7:1 (AAA) Large text: 3:1 minimum (AA), 4.5:1 (AAA) Large text = 18pt regular or 14pt bold */
Anti-Patterns to Avoid
/* 1. Overusing !important */ .title { color: red !important; /* Now impossible to override normally */ } /* 2. Overly specific selectors */ div#main .content ul.nav li a.link { } /* Nightmare specificity */ /* 3. Magic numbers */ .dropdown { top: 37px; /* Why 37? Use a variable or calc() */ left: -9px; /* What is this compensating for? */ } /* 4. Styling with IDs */ #sidebar { } /* Use .sidebar instead */ /* 5. Not using shorthand when appropriate */ .box { margin-top: 10px; margin-right: 20px; margin-bottom: 10px; margin-left: 20px; /* Use: margin: 10px 20px; */ } /* 6. Using tag names in selectors */ div.card { } /* Just use .card — more flexible */ /* 7. Undoing styles */ .list li { border-bottom: 1px solid; } .list li:last-child { border-bottom: none; } /* Better: .list li + li { border-top: 1px solid; } */
Gotchas
.search-results__item-thumbnail--loading is hard to read. Keep block names short and consider splitting into separate blocks when names get unwieldy.
Pro Tips
stylelint-config-standard as a starting point and customize rules for your team.
:focus-visible gives you the best of both worlds — visible focus for keyboard, hidden for mouse.
content-visibility: auto for long pages. It tells the browser to skip rendering off-screen sections until they're needed, dramatically improving initial load performance on content-heavy pages.