CSS Architecture & Best Practices
Small stylesheets are easy. Large ones, maintained by many people for years, need structure: consistent naming, a design system of tokens, predictable specificity, and tooling. This lesson covers how teams keep CSS manageable — and fast.
Start with a reset and base styles
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
margin: 0;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
img,
picture,
video,
canvas,
svg {
display: block;
max-width: 100%;
}
input,
button,
textarea,
select {
font: inherit;
}
p,
h1,
h2,
h3 {
overflow-wrap: break-word;
}
A small modern reset removes the most common browser inconsistencies and annoyances. Base styles then set typography and colours for plain elements.
Design tokens
:root {
/* colour */
--color-text: #10131c;
--color-muted: #565d72;
--color-accent: #d6336c;
--color-surface: #ffffff;
/* spacing scale */
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 1rem;
--space-4: 1.5rem;
--space-5: 2.5rem;
/* type scale */
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.25rem;
--text-xl: clamp(1.75rem, 1.2rem + 2vw, 2.5rem);
/* other */
--radius: 8px;
--shadow-1: 0 2px 6px rgb(0 0 0 / 10%);
}
Tokens are the design's decisions captured as variables. Using var(--space-3) instead of a random 17px keeps spacing consistent, and a redesign becomes a matter of changing tokens.
Naming methodologies
BEM
.card { padding: 1rem; }
.card__title { font-size: 1.25rem; }
.card__button { margin-top: 0.5rem; }
.card--featured { border: 2px solid gold; }
Block, Element, Modifier: every class says which component it belongs to. Flat, low-specificity selectors and no surprises about what a class affects.
Utility-first
<div class="p-4 rounded-lg shadow bg-white"> <h3 class="text-lg font-bold mb-2">Card title</h3> </div>
Frameworks like Tailwind CSS provide thousands of single-purpose classes and you compose them in HTML. It avoids naming things and dead CSS, at the cost of longer class attributes. Many teams combine approaches: components for complex pieces, utilities for small adjustments.
Scoped CSS
Component frameworks often scope styles automatically — CSS Modules, Vue's <style scoped>, Svelte, and native @scope or Shadow DOM — so class names can't collide between components.
Organising files
css/ ├── main.css /* imports in layer order */ ├── reset.css ├── tokens.css ├── base.css /* element styles */ ├── layout.css /* page regions */ ├── components/ │ ├── button.css │ ├── card.css │ └── nav.css └── utilities.css
@layer reset, tokens, base, layout, components, utilities; @import url("reset.css") layer(reset); @import url("tokens.css") layer(tokens); @import url("base.css") layer(base); @import url("components/button.css") layer(components); @import url("utilities.css") layer(utilities);
Ordering files from general to specific, and putting them in cascade layers (lesson 45), keeps the cascade predictable. In production, a build tool bundles them into one file.
Specificity discipline
- Style with classes; keep ids and long chains out of CSS.
- Avoid
!importantexcept in utilities designed to always win. - Use
:where()for low-specificity defaults. - Prefer adding a modifier class over writing a more specific selector.
Writing maintainable CSS
- Name by purpose, not appearance:
.alert-error, not.red-box. - Avoid magic numbers:
top: 37pxis a mystery; a token or a comment explains it. - Let content define size: prefer
min-height, intrinsic sizing andgapover fixed dimensions and margins. - Delete unused CSS. Dead styles accumulate; tools can detect unused selectors.
- Comment the why, especially for browser workarounds.
Performance
- CSS blocks rendering: keep the critical stylesheet small, and don't
@importchains at runtime without a bundler (each import is another round trip). - Animate
transformandopacity; avoid animating layout properties. - Use
content-visibility: autofor long, off-screen sections. - Limit web fonts and use
font-display. - Minify and compress CSS in production.
Tooling
| Tool | Purpose |
|---|---|
| Stylelint | Catches errors (unknown properties, invalid values) and enforces conventions — every example in this course was checked with it |
| Prettier | Consistent formatting |
| Lightning CSS / PostCSS | Bundling, minifying, adding fallbacks for older browsers |
| Sass | A preprocessor with mixins, functions and partials — less essential now that CSS has variables, nesting and layers |
| Browser DevTools | Inspect computed styles, grid/flex overlays, coverage of unused CSS |
Browser support
Before using a new feature, check its support on caniuse.com or MDN's compatibility tables. Thanks to CSS's forgiving error handling, you can often use new features as progressive enhancements: older browsers ignore what they don't understand. @supports lets you test for a feature explicitly:
.gallery {
display: flex;
flex-wrap: wrap;
}
@supports (display: grid) and (grid-template-rows: subgrid) {
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
}