CSS Architecture and Methodology
1. CSS Naming Conventions (BEM, OOCSS, SMACSS)
| Methodology | Pattern | Description | Example |
|---|---|---|---|
| BEM (Block Element Modifier) | .block__element--modifier |
Strict naming convention for components | .card__title--large |
| BEM Block | .block |
Standalone component/entity | .menu, .button |
| BEM Element | .block__element |
Part of block, no standalone meaning | .menu__item, .button__icon |
| BEM Modifier | .block--modifier |
Different state or version of block | .button--primary, .menu--vertical |
| OOCSS (Object Oriented CSS) | Separate structure and skin | Reusable visual patterns as objects | .media, .button |
| SMACSS (Scalable Modular Architecture) | 5 categories of rules | Base, Layout, Module, State, Theme | .l-sidebar, .is-active |
| ITCSS (Inverted Triangle CSS) | Layered architecture | Generic to specific, low to high specificity | Settings → Tools → Generic → Elements → Objects → Components → Utilities |
| CUBE CSS | Composition, Utility, Block, Exception | Modern methodology with design tokens | Combine composition classes with utilities |
Example: BEM naming convention
/* Block: standalone component */
.card {
display: flex;
flex-direction: column;
background: white;
border-radius: 8px;
padding: 1rem;
}
/* Elements: parts of the block */
.card__header {
display: flex;
justify-content: space-between;
margin-bottom: 1rem;
}
.card__title {
font-size: 1.5rem;
font-weight: bold;
}
.card__subtitle {
font-size: 0.875rem;
color: #666;
}
.card__body {
flex: 1;
line-height: 1.6;
}
.card__footer {
display: flex;
gap: 0.5rem;
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid #eee;
}
.card__button {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
}
/* Modifiers: variations */
.card--featured {
border: 2px solid #007acc;
box-shadow: 0 4px 8px rgba(0,122,204,0.2);
}
.card--compact {
padding: 0.5rem;
}
.card__title--large {
font-size: 2rem;
}
.card__button--primary {
background: #007acc;
color: white;
}
.card__button--secondary {
background: transparent;
border: 1px solid #007acc;
color: #007acc;
}
/* HTML structure */
/*
<div class="card card--featured">
<div class="card__header">
<h2 class="card__title card__title--large">Title</h2>
</div>
<div class="card__body">Content</div>
<div class="card__footer">
<button class="card__button card__button--primary">Action</button>
<button class="card__button card__button--secondary">Cancel</button>
</div>
</div>
*/
Example: OOCSS (Object Oriented CSS)
/* Separate structure from skin */
/* Structure: layout and positioning */
.media {
display: flex;
align-items: flex-start;
}
.media__figure {
margin-right: 1rem;
}
.media__body {
flex: 1;
}
/* Skin: visual appearance */
.skin-default {
background: white;
border: 1px solid #ddd;
border-radius: 4px;
padding: 1rem;
}
.skin-primary {
background: #007acc;
color: white;
padding: 1rem;
}
/* Combine structure and skin */
/*
<div class="media skin-default">
<div class="media__figure">...</div>
<div class="media__body">...</div>
</div>
*/
/* Separate container from content */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}
.box {
padding: 1rem;
margin-bottom: 1rem;
}
/* Content is independent of container */
.box-content {
line-height: 1.6;
}
/* Reusable button object */
.btn {
display: inline-block;
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
text-align: center;
text-decoration: none;
}
/* Skin variations */
.btn-primary {
background: #007acc;
color: white;
}
.btn-secondary {
background: #6c757d;
color: white;
}
.btn-large {
padding: 0.75rem 1.5rem;
font-size: 1.125rem;
}
Example: SMACSS categories
/* 1. Base: defaults, no classes */
* {
box-sizing: border-box;
}
body {
font-family: sans-serif;
line-height: 1.6;
color: #333;
}
a {
color: #007acc;
text-decoration: none;
}
/* 2. Layout: major page sections (prefix: l-) */
.l-header {
position: fixed;
top: 0;
width: 100%;
z-index: 100;
}
.l-sidebar {
width: 250px;
float: left;
}
.l-main {
margin-left: 250px;
}
.l-footer {
clear: both;
padding: 2rem 0;
}
/* 3. Module: reusable components */
.card {
background: white;
border: 1px solid #ddd;
border-radius: 4px;
padding: 1rem;
}
.button {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
}
.nav {
list-style: none;
padding: 0;
}
.nav-item {
display: inline-block;
margin-right: 1rem;
}
/* 4. State: describe behavior (prefix: is- or has-) */
.is-active {
font-weight: bold;
color: #007acc;
}
.is-hidden {
display: none;
}
.is-disabled {
opacity: 0.5;
pointer-events: none;
}
.has-error {
border-color: #dc3545;
}
.is-loading {
cursor: wait;
opacity: 0.6;
}
/* 5. Theme: color schemes and typography */
.theme-dark {
background: #1a1a1a;
color: #f0f0f0;
}
.theme-dark .card {
background: #2d2d2d;
border-color: #404040;
}
.theme-large-text {
font-size: 1.125rem;
}
Note: BEM is most popular for component-based development. Use
__ for elements, -- for modifiers. OOCSS focuses on reusability. SMACSS provides
categorization. Choose based on team preference and project needs.
2. CSS-in-JS vs CSS Modules vs Utility-first (Tailwind)
| Approach | Description | Pros | Cons |
|---|---|---|---|
| CSS-in-JS | Styles written in JavaScript (styled-components, Emotion) | Scoped by default, dynamic styling, no separate files | Runtime overhead, larger bundle, no caching |
| CSS Modules | Locally scoped CSS files (styles.module.css) | Automatic scoping, standard CSS, build-time processing | Extra build step, separate files |
| Utility-first (Tailwind) | Pre-defined utility classes in HTML | Fast development, small bundle, consistency | HTML clutter, learning curve, harder customization |
| Traditional CSS | Global stylesheets with naming conventions | Standard, cacheable, no build needed | Global scope, naming conflicts, larger files |
| CSS Preprocessors (Sass/Less) | Extended CSS with variables, nesting, mixins | DRY, powerful features, compiles to CSS | Build step required, can generate bloat |
| Atomic CSS | Single-purpose utility classes | Highly reusable, small CSS, fast | Many classes in HTML, specificity issues |
Example: CSS-in-JS (styled-components style)
/* JavaScript/TypeScript file */
/*
import styled from 'styled-components';
// Styled component
const Button = styled.button`
padding: 0.75rem 1.5rem;
background: ${props => props.primary ? '#007acc' : '#6c757d'};
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
&:hover {
background: ${props => props.primary ? '#005a9e' : '#5a6268'};
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
`;
// Dynamic styles
const Card = styled.div`
background: white;
padding: ${props => props.compact ? '0.5rem' : '1rem'};
border-radius: 8px;
box-shadow: ${props => props.elevated ? '0 4px 8px rgba(0,0,0,0.1)' : 'none'};
`;
// Extending styles
const PrimaryButton = styled(Button)`
background: #007acc;
font-weight: bold;
`;
// Using theme
const ThemedCard = styled.div`
background: ${props => props.theme.cardBg};
color: ${props => props.theme.textColor};
padding: 1rem;
`;
// Usage in JSX
function App() {
return (
<Card elevated>
<Button primary>Primary</Button>
<Button>Secondary</Button>
<PrimaryButton>Important</PrimaryButton>
</Card>
);
}
*/
/* Advantages:
- Automatic scoping (no class name conflicts)
- Dynamic styling based on props
- Colocated with component logic
- Dead code elimination
Disadvantages:
- Runtime overhead
- No style caching between renders
- Larger JavaScript bundle
- Debugging can be harder */
Example: CSS Modules
/* styles.module.css */
.card {
background: white;
padding: 1rem;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.card-title {
font-size: 1.5rem;
margin-bottom: 0.5rem;
color: #333;
}
.card-body {
line-height: 1.6;
color: #666;
}
.button {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
}
.button-primary {
background: #007acc;
color: white;
}
/* Composes (reuse other classes) */
.button-large {
composes: button;
padding: 0.75rem 1.5rem;
font-size: 1.125rem;
}
/* Global styles */
:global(.global-class) {
/* Not scoped */
}
/* JavaScript usage */
/*
import styles from './styles.module.css';
function Card() {
return (
<div className={styles.card}>
<h2 className={styles.cardTitle}>Title</h2>
<p className={styles.cardBody}>Content</p>
<button className={styles.buttonPrimary}>Action</button>
</div>
);
}
// Generated HTML:
// <div class="styles_card__Ab3D">
// <h2 class="styles_cardTitle__Xy2Z">Title</h2>
// </div>
*/
/* Advantages:
- Automatic local scoping
- Standard CSS syntax
- Build-time processing
- Works with preprocessors
Disadvantages:
- Build step required
- Import in every component
- Separate files to maintain */
Example: Utility-first (Tailwind CSS style)
/* Configuration (tailwind.config.js) */
/*
module.exports = {
theme: {
extend: {
colors: {
primary: '#007acc',
}
}
}
}
*/
/* HTML with utility classes */
/*
<div class="bg-white rounded-lg shadow-md p-4 max-w-md mx-auto">
<h2 class="text-2xl font-bold mb-2 text-gray-800">Card Title</h2>
<p class="text-gray-600 leading-relaxed mb-4">
Card content with consistent spacing and typography.
</p>
<div class="flex gap-2">
<button class="bg-primary text-white px-4 py-2 rounded hover:bg-blue-700 transition">
Primary
</button>
<button class="bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600 transition">
Secondary
</button>
</div>
</div>
<!-- Responsive utilities -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<!-- Content -->
</div>
<!-- State variants -->
<button class="bg-blue-500 hover:bg-blue-700 active:bg-blue-800 focus:ring-2 focus:ring-blue-300">
Button
</button>
*/
/* Custom utilities (if needed) */
@layer utilities {
.text-balance {
text-wrap: balance;
}
.bg-grid {
background-image:
linear-gradient(#e5e5e5 1px, transparent 1px),
linear-gradient(90deg, #e5e5e5 1px, transparent 1px);
background-size: 20px 20px;
}
}
/* Component extraction */
@layer components {
.btn-primary {
@apply bg-primary text-white px-4 py-2 rounded hover:bg-blue-700 transition;
}
.card {
@apply bg-white rounded-lg shadow-md p-4;
}
}
/* Advantages:
- Very fast development
- Small CSS bundle (unused purged)
- Consistent design system
- No naming required
Disadvantages:
- Many classes in HTML
- Learning curve for syntax
- Harder to read HTML
- Customization requires config */
Example: Comparison of approaches
/* Traditional CSS */
.button {
padding: 0.5rem 1rem;
background: #007acc;
color: white;
border: none;
border-radius: 4px;
}
/* HTML: <button class="button">Click</button> */
/* CSS Modules (button.module.css) */
.button {
padding: 0.5rem 1rem;
background: #007acc;
color: white;
border: none;
border-radius: 4px;
}
/* JS: import styles from './button.module.css' */
/* HTML: <button className={styles.button}>Click</button> */
/* CSS-in-JS (styled-components) */
/*
const Button = styled.button`
padding: 0.5rem 1rem;
background: #007acc;
color: white;
border: none;
border-radius: 4px;
`;
HTML: <Button>Click</Button>
*/
/* Utility-first (Tailwind) */
/* HTML: <button class="px-4 py-2 bg-blue-600 text-white rounded">Click</button> */
/* Decision matrix:
- Large team, consistency → Utility-first (Tailwind)
- React/component focus → CSS-in-JS or CSS Modules
- Simple sites, fast load → Traditional CSS
- Build complexity tolerance → Any modern approach
- Dynamic theming needs → CSS Variables + any approach */
Warning: CSS-in-JS has runtime cost. CSS Modules require build step. Utility-first creates HTML
bloat. Choose based on team, project size, and performance needs. Modern approaches: CSS Modules + CSS
Variables, or Tailwind with @apply for extraction.
3. Component-scoped CSS Strategies
| Strategy | Technique | Scope Mechanism | Use Case |
|---|---|---|---|
| Shadow DOM | Web Components encapsulation | Browser-native isolation | True component isolation, framework-agnostic |
| CSS Modules | Build-time class name hashing | Unique class names per component | React, Vue, Angular projects |
| Scoped Styles (Vue) | Add unique attribute to elements | Attribute selectors | Vue single-file components |
| CSS-in-JS | Generate unique class names at runtime | Automatic hashing | React applications |
| @scope NEW | Native CSS scoping rule | Lexical scoping boundaries | Modern browsers, framework-agnostic |
| BEM Convention | Strict naming methodology | Manual namespacing | Any project, no build required |
| CSS Cascade Layers | @layer for specificity control | Layer-based isolation | Organize component styles by priority |
Example: Shadow DOM component scoping
/* Web Component with Shadow DOM */
/*
class MyCard extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
/* Styles are completely isolated */
:host {
display: block;
background: white;
border-radius: 8px;
padding: 1rem;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.title {
font-size: 1.5rem;
margin-bottom: 0.5rem;
}
.content {
line-height: 1.6;
}
::slotted(button) {
padding: 0.5rem 1rem;
background: #007acc;
color: white;
border: none;
border-radius: 4px;
}
</style>
<div class="card">
<h2 class="title"><slot name="title"></slot></h2>
<div class="content"><slot></slot></div>
<slot name="actions"></slot>
</div>
`;
}
}
customElements.define('my-card', MyCard);
// Usage:
<my-card>
<span slot="title">Card Title</span>
<p>Card content</p>
<button slot="actions">Action</button>
</my-card>
*/
/* Advantages:
- True encapsulation
- No style leaking
- Framework-agnostic
- Browser-native
Challenges:
- Cannot style from outside
- Limited global theme support
- Learning curve */
Example: CSS @scope (modern native scoping)
/* Native CSS scoping (Chrome 118+) */
/* Scope styles to .card component */
@scope (.card) {
/* Only applies inside .card */
h2 {
font-size: 1.5rem;
margin-bottom: 0.5rem;
}
p {
line-height: 1.6;
color: #666;
}
button {
padding: 0.5rem 1rem;
background: #007acc;
color: white;
border: none;
border-radius: 4px;
}
}
/* Scope with exclusion boundary */
@scope (.card) to (.nested-card) {
/* Applies inside .card but NOT inside .nested-card */
p {
color: #333;
}
}
/* Multiple scopes */
@scope (.header) {
nav {
display: flex;
gap: 1rem;
}
}
@scope (.footer) {
nav {
display: block; /* Different nav styling */
}
}
/* Using :scope pseudo-class */
@scope (.card) {
:scope {
/* Styles the .card itself */
background: white;
padding: 1rem;
border-radius: 8px;
}
:scope > h2 {
/* Direct child h2 of .card */
border-bottom: 2px solid #007acc;
}
}
/* Practical example: Modal component */
@scope (.modal) {
:scope {
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0,0,0,0.5);
}
.modal-content {
background: white;
padding: 2rem;
border-radius: 8px;
max-width: 500px;
width: 90%;
}
h2 {
margin-top: 0;
}
button {
margin-top: 1rem;
}
}
Example: Vue scoped styles
/* Vue Single File Component */
/*
<template>
<div class="card">
<h2 class="card-title">{{ title }}</h2>
<p class="card-content">{{ content }}</p>
<button class="card-button">Action</button>
</div>
</template>
<style scoped>
.card {
background: white;
padding: 1rem;
border-radius: 8px;
}
.card-title {
font-size: 1.5rem;
margin-bottom: 0.5rem;
}
.card-content {
line-height: 1.6;
color: #666;
}
.card-button {
padding: 0.5rem 1rem;
background: #007acc;
color: white;
border: none;
border-radius: 4px;
}
/* Generated HTML:
<div class="card" data-v-f3f3eg9>
<h2 class="card-title" data-v-f3f3eg9>...</h2>
</div>
Generated CSS:
.card[data-v-f3f3eg9] { ... }
.card-title[data-v-f3f3eg9] { ... }
*/
/* Deep selector for child components */
.card :deep(.child-component) {
margin-top: 1rem;
}
/* Slotted content */
.card :slotted(button) {
margin-left: 0.5rem;
}
/* Global styles within scoped */
:global(.global-class) {
/* Not scoped */
}
</style>
*/
Example: Cascade layers for component organization
/* Define layer order (low to high priority) */
@layer reset, base, components, utilities, overrides;
/* Reset layer (lowest priority) */
@layer reset {
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
}
/* Base layer */
@layer base {
body {
font-family: sans-serif;
line-height: 1.6;
color: #333;
}
a {
color: #007acc;
}
}
/* Components layer */
@layer components {
.card {
background: white;
padding: 1rem;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.button {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
}
.button-primary {
background: #007acc;
color: white;
}
}
/* Utilities layer */
@layer utilities {
.text-center {
text-align: center;
}
.mt-1 {
margin-top: 0.5rem;
}
.hidden {
display: none;
}
}
/* Overrides layer (highest priority) */
@layer overrides {
.force-visible {
display: block !important;
}
}
/* Unlayered styles have highest priority */
.emergency-override {
color: red;
}
/* Import layers */
@import url('reset.css') layer(reset);
@import url('components.css') layer(components);
/* Nested layers */
@layer components {
@layer cards, buttons, forms;
@layer cards {
.card { /* ... */ }
}
@layer buttons {
.button { /* ... */ }
}
}
Note: Use Shadow DOM for true isolation, CSS Modules for React/build tools, @scope for modern
native scoping. Cascade layers organize component priority. BEM works everywhere without tooling.
4. CSS Reset vs Normalize vs Modern-normalize
| Approach | Philosophy | Size | Use Case |
|---|---|---|---|
| CSS Reset (Meyer/Universal) | Remove ALL browser defaults | ~1-2 KB | Start from absolute zero, full control |
| Normalize.css | Preserve useful defaults, fix bugs | ~8 KB | Consistent cross-browser styling |
| Modern-normalize | Normalize for modern browsers only | ~2 KB | Modern browsers (no IE support) |
| Sanitize.css | Normalize + opinionated defaults | ~10 KB | Good defaults out of the box |
| Custom Reset | Minimal project-specific reset | <1 KB | Tailored to specific needs |
Example: CSS Reset (Meyer style)
/* Classic CSS Reset - removes everything */
* {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
/* Modern universal reset */
*, *::before, *::after {
box-sizing: border-box;
}
* {
margin: 0;
padding: 0;
}
html, body {
height: 100%;
}
body {
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, h4, h5, h6 {
overflow-wrap: break-word;
}
/* Advantages:
- Complete control
- Minimal file size
- No surprises
Disadvantages:
- Must rebuild everything
- Removes useful defaults */
Example: Modern-normalize (modern browsers)
/* Modern-normalize highlights (simplified) */
/* Document */
*, *::before, *::after {
box-sizing: border-box;
}
html {
line-height: 1.15;
-webkit-text-size-adjust: 100%;
tab-size: 4;
}
body {
margin: 0;
font-family: system-ui, -apple-system, sans-serif;
}
/* Sections */
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/* Grouping content */
hr {
height: 0;
color: inherit;
}
/* Text-level semantics */
abbr[title] {
text-decoration: underline dotted;
}
b, strong {
font-weight: bolder;
}
code, kbd, samp, pre {
font-family: ui-monospace, monospace;
font-size: 1em;
}
small {
font-size: 80%;
}
/* Forms */
button, input, optgroup, select, textarea {
font-family: inherit;
font-size: 100%;
line-height: 1.15;
margin: 0;
}
button, select {
text-transform: none;
}
button, [type='button'], [type='reset'], [type='submit'] {
-webkit-appearance: button;
}
/* Interactive */
summary {
display: list-item;
}
/* Advantages:
- Keeps useful defaults
- Cross-browser consistency
- Well-documented
- Small size for modern browsers */
Example: Custom minimal reset (modern approach)
/* Minimal modern reset - best practices */
/* Box sizing */
*, *::before, *::after {
box-sizing: border-box;
}
/* Remove default margin */
* {
margin: 0;
}
/* Body defaults */
body {
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
/* Media defaults */
img, picture, video, canvas, svg {
display: block;
max-width: 100%;
}
/* Form element font inheritance */
input, button, textarea, select {
font: inherit;
}
/* Text overflow */
p, h1, h2, h3, h4, h5, h6 {
overflow-wrap: break-word;
}
/* Remove list styles on ul, ol with role="list" */
ul[role='list'], ol[role='list'] {
list-style: none;
}
/* Root stacking context */
#root, #__next {
isolation: isolate;
}
/* Focus visible */
:focus-visible {
outline: 2px solid currentColor;
outline-offset: 2px;
}
/* Remove animations for people who've turned them off */
@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;
}
}
/* Additional useful resets */
a {
color: inherit;
text-decoration: none;
}
button {
cursor: pointer;
background: none;
border: none;
padding: 0;
}
/* This reset:
- Minimal but effective
- Modern best practices
- Accessibility-aware
- Easy to customize */
Example: Comparison and decision guide
/* Decision matrix:
1. Use CSS Reset (Meyer/Universal) when:
- You want absolute control
- Building from scratch
- Don't need semantic defaults
- Want smallest possible base
2. Use Normalize.css when:
- Need cross-browser consistency
- Want to preserve useful defaults
- Supporting older browsers
- Standard approach
3. Use Modern-normalize when:
- Only supporting modern browsers
- Want normalize benefits but smaller
- No IE11 support needed
- Modern project (2020+)
4. Use Custom/Minimal Reset when:
- Have specific requirements
- Want to understand every line
- Very small footprint needed
- Project-specific needs
5. Use Sanitize.css when:
- Want opinionated defaults
- Need forms to look consistent
- Want normalize + extras
- Don't want to set common styles
*/
/* Recommended modern approach: */
/* 1. Start with modern-normalize or minimal reset */
/* 2. Add project-specific base styles */
/* 3. Use CSS custom properties for tokens */
/* 4. Build component styles on top */
/* Example base after reset */
:root {
--font-sans: system-ui, -apple-system, sans-serif;
--font-mono: ui-monospace, monospace;
--color-text: #1a1a1a;
--color-bg: #ffffff;
--spacing-unit: 0.5rem;
}
body {
font-family: var(--font-sans);
color: var(--color-text);
background: var(--color-bg);
}
h1, h2, h3, h4, h5, h6 {
font-weight: 600;
line-height: 1.2;
}
code, pre {
font-family: var(--font-mono);
}
a {
color: #007acc;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
Warning: Don't use both reset and normalize together. CSS Reset removes useful defaults that
you'll need to rebuild. Modern projects should use modern-normalize or custom
minimal reset. Always include
box-sizing: border-box.
5. Critical CSS and Above-fold Optimization
| Technique | Description | Impact | Implementation |
|---|---|---|---|
| Critical CSS | Inline above-fold styles in HTML | Eliminates render-blocking CSS | Extract and inline critical styles |
| CSS Splitting | Separate critical from non-critical | Faster initial render | Build tool configuration |
| Async CSS Loading | Load non-critical CSS asynchronously | Non-blocking page load | Media query trick or JS |
| Preload | Hint browser to load CSS early | Faster CSS discovery | <link rel="preload"> |
| CSS Containment | Isolate subtrees for rendering | Better paint performance | contain: layout paint |
| Content-visibility | Skip rendering off-screen content | Faster initial render | content-visibility: auto |
Example: Critical CSS inline pattern
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Critical CSS inlined -->
<style>
/* Only above-the-fold styles */
body {
margin: 0;
font-family: system-ui, sans-serif;
line-height: 1.6;
}
.header {
background: #fff;
border-bottom: 1px solid #e0e0e0;
padding: 1rem 2rem;
}
.hero {
min-height: 60vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.hero h1 {
font-size: clamp(2rem, 5vw, 4rem);
margin: 0;
}
/* Loading state for below-fold */
.below-fold {
min-height: 100px;
background: #f5f5f5;
}
</style>
<!-- Preload full stylesheet -->
<link rel="preload" href="/styles/main.css" as="style">
<!-- Async load non-critical CSS -->
<link rel="stylesheet" href="/styles/main.css" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="/styles/main.css"></noscript>
</head>
<body>
<header class="header">
<!-- Header content -->
</header>
<section class="hero">
<h1>Welcome</h1>
</section>
<main class="below-fold">
<!-- Rest of content -->
</main>
</body>
</html>
/* Tools for extracting critical CSS:
- Critical (npm package)
- PurgeCSS
- Critters (Next.js/Angular)
- Penthouse
- Online tools: criticalcss.com
*/
Example: Async CSS loading patterns
<!-- Method 1: Media query trick (most compatible) -->
<link rel="stylesheet" href="styles.css" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="styles.css"></noscript>
<!-- Method 2: JavaScript loading -->
<script>
function loadCSS(href) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = href;
document.head.appendChild(link);
}
// Load when DOM is ready
if (document.readyState === 'complete') {
loadCSS('/styles/main.css');
} else {
window.addEventListener('load', () => loadCSS('/styles/main.css'));
}
</script>
<!-- Method 3: Preload + async apply -->
<link rel="preload" href="styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="styles.css"></noscript>
<!-- Method 4: LoadCSS library (polyfill) -->
<script>
/*! loadCSS. [c]2017 Filament Group, Inc. MIT License */
(function(w){
"use strict";
var loadCSS = function(href, before, media){
var doc = w.document;
var ss = doc.createElement("link");
var ref;
if(before){
ref = before;
} else {
var refs = (doc.body || doc.getElementsByTagName("head")[0]).childNodes;
ref = refs[refs.length - 1];
}
var sheets = doc.styleSheets;
ss.rel = "stylesheet";
ss.href = href;
ss.media = "only x";
function ready(cb){
if(doc.body){
return cb();
}
setTimeout(function(){
ready(cb);
});
}
ready(function(){
ref.parentNode.insertBefore(ss, (before ? ref : ref.nextSibling));
});
var onloadcssdefined = function(cb){
var resolvedHref = ss.href;
var i = sheets.length;
while(i--){
if(sheets[i].href === resolvedHref){
return cb();
}
}
setTimeout(function(){
onloadcssdefined(cb);
});
};
function loadCB(){
if(ss.addEventListener){
ss.removeEventListener("load", loadCB);
}
ss.media = media || "all";
}
if(ss.addEventListener){
ss.addEventListener("load", loadCB);
}
ss.onloadcssdefined = onloadcssdefined;
onloadcssdefined(loadCB);
return ss;
};
if(typeof exports !== "undefined"){
exports.loadCSS = loadCSS;
} else {
w.loadCSS = loadCSS;
}
}(typeof global !== "undefined" ? global : this));
// Usage
loadCSS("/path/to/stylesheet.css");
</script>
Example: Content-visibility optimization
/* Optimize rendering of off-screen content */
/* Skip rendering until near viewport */
.article-section {
content-visibility: auto;
contain-intrinsic-size: auto 500px; /* Estimated height */
}
/* Critical above-fold content */
.hero {
content-visibility: visible; /* Always render */
}
/* Below-fold content */
.comments-section {
content-visibility: auto;
contain-intrinsic-size: auto 800px;
}
.related-articles {
content-visibility: auto;
contain-intrinsic-size: auto 400px;
}
/* Long list optimization */
.long-list-item {
content-visibility: auto;
contain-intrinsic-size: auto 100px;
}
/* Performance gains:
- Faster initial render
- Reduced layout calculation
- Lower memory usage
- Smoother scrolling
*/
/* CSS Containment for isolated components */
.card {
contain: layout paint;
/* Layout and paint isolated from rest of page */
}
.widget {
contain: layout style paint;
/* Even more isolated */
}
/* Full containment */
.independent-component {
contain: strict;
/* Maximum isolation, but requires size */
width: 300px;
height: 400px;
}
/* Use for:
- Independent widgets
- List items
- Cards
- Modals
- Sidebars
*/
Example: Build-time optimization strategies
/* webpack.config.js or similar build config */
/*
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = {
optimization: {
minimizer: [
new CssMinimizerPlugin(),
],
splitChunks: {
cacheGroups: {
styles: {
name: 'styles',
type: 'css/mini-extract',
chunks: 'all',
enforce: true,
},
},
},
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
}),
],
};
*/
/* PostCSS configuration */
/*
module.exports = {
plugins: [
require('autoprefixer'),
require('cssnano')({
preset: ['default', {
discardComments: {
removeAll: true,
},
}],
}),
require('@fullhuman/postcss-purgecss')({
content: ['./src/!**!/!*.html', './src/!**!/!*.js'],
defaultExtractor: content => content.match(/[\w-/:]+(?<!:)/g) || []
}),
],
};
*/
/* Critical CSS extraction (Node.js) */
/*
const critical = require('critical');
critical.generate({
base: 'dist/',
src: 'index.html',
target: 'index-critical.html',
inline: true,
width: 1300,
height: 900,
minify: true,
});
*/
/* Performance checklist:
1. Inline critical CSS (<14KB gzipped)
2. Async load non-critical CSS
3. Remove unused CSS (PurgeCSS)
4. Minify CSS (cssnano)
5. Use content-visibility for long pages
6. Add CSS containment to components
7. Preload key stylesheets
8. Split CSS by route/page
9. Use HTTP/2 server push (optional)
10. Monitor CSS bundle size
*/
CSS Architecture Best Practices
- Choose naming convention early: BEM for components, prefixes for SMACSS
- CSS Modules or
@scopefor automatic scoping in modern apps - Utility-first (Tailwind) for rapid development, extract components with @apply
- Use modern-normalize or custom minimal reset for modern browsers
- Inline critical CSS (<14KB), async load rest for best performance
- Apply
content-visibility: autoto off-screen sections - Use
contain: layout painton independent components - Organize with cascade layers (@layer) for predictable specificity
- Split CSS by route/page, preload critical resources
- Monitor bundle size, remove unused CSS with PurgeCSS