CSS has transformed dramatically in the last decade. Grid and Flexbox replaced float-based layouts; custom properties enabled runtime theming; container queries solved context-aware responsiveness. This guide takes you from CSS fundamentals through the most powerful modern features, with real patterns you'll use every day.
Step 1 — Selectors, Specificity & the Cascade
The cascade determines which CSS rule wins when multiple rules target the same element. Understanding specificity is the key to writing predictable styles.
/* Specificity is calculated as (inline, id, class/attr/pseudo, element) */
p { color: black; } /* 0,0,0,1 */
.text { color: blue; } /* 0,0,1,0 */
#intro { color: red; } /* 0,1,0,0 */
style="color: green" /* 1,0,0,0 — always wins (avoid) */
/* When specificity is equal, the LAST rule wins (cascade order) */
.btn { background: blue; }
.btn { background: green; } /* green wins — last declared */
/* !important overrides everything — use sparingly */
.override { color: purple !important; }
/* :is() and :where() for readable complex selectors */
/* :is() keeps the specificity of its highest argument */
:is(header, main, footer) p { margin: 0; }
/* :where() has ZERO specificity — great for resets */
:where(ul, ol) { list-style: none; padding: 0; }Cascade Layers (@layer)
- @layer lets you explicitly control the cascade order, resolving specificity wars.
- Styles in later layers win over earlier layers, regardless of specificity.
- Use layers for: resets, third-party, base, components, utilities, overrides.
@layer reset, base, components, utilities;
@layer reset {
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
}
@layer base {
body { font-family: 'Inter', sans-serif; line-height: 1.6; }
a { color: var(--color-primary); }
}
@layer components {
.btn { padding: 0.5rem 1rem; border-radius: 6px; }
}
@layer utilities {
.mt-4 { margin-top: 1rem; }
.text-center { text-align: center; }
}Step 2 — CSS Custom Properties (Variables)
CSS custom properties (variables) enable design tokens — a single source of truth for your design system. They cascade like any other property and can be updated at runtime with JavaScript.
:root {
/* Color palette */
--color-primary: hsl(221, 83%, 53%);
--color-primary-dark: hsl(221, 83%, 40%);
--color-surface: hsl(0, 0%, 100%);
--color-on-surface: hsl(0, 0%, 10%);
--color-muted: hsl(0, 0%, 45%);
/* Spacing scale (based on 4px) */
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-4: 1rem;
--space-8: 2rem;
--space-16: 4rem;
/* Typography */
--font-sans: 'Inter', system-ui, sans-serif;
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
--font-size-lg: 1.125rem;
--font-size-xl: 1.25rem;
--font-size-2xl: 1.5rem;
/* Border & Shadow */
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 16px;
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
}
/* Dynamic dark mode: only change the variables */
.dark {
--color-surface: hsl(220, 13%, 10%);
--color-on-surface: hsl(220, 13%, 95%);
--color-muted: hsl(220, 13%, 60%);
}
/* Usage */
.card {
background: var(--color-surface);
color: var(--color-on-surface);
border-radius: var(--radius-md);
padding: var(--space-4);
box-shadow: var(--shadow-md);
}
/* Fallback value syntax */
.btn {
background: var(--btn-color, var(--color-primary));
}Step 3 — Flexbox: Complete Guide
Flexbox is a one-dimensional layout system. It distributes space along a single axis (row or column). It excels at aligning items inside a container and building navigation bars, card rows, and form layouts.
/* === FLEX CONTAINER PROPERTIES === */
.container {
display: flex; /* Enable flexbox */
flex-direction: row; /* row | row-reverse | column | column-reverse */
flex-wrap: wrap; /* nowrap | wrap | wrap-reverse */
gap: 1rem; /* Space between items (replaces margin hacks) */
/* Main-axis alignment (along flex-direction) */
justify-content: space-between; /* flex-start | flex-end | center | space-around | space-evenly */
/* Cross-axis alignment (perpendicular to flex-direction) */
align-items: center; /* stretch | flex-start | flex-end | baseline */
/* Multi-line alignment */
align-content: flex-start; /* flex-start | center | space-between | ... */
}
/* === FLEX ITEM PROPERTIES === */
.item {
/* Shorthand: flex-grow flex-shrink flex-basis */
flex: 1 1 200px; /* grow=1, shrink=1, basis=200px */
/* Equivalent to: */
flex-grow: 1; /* How much to grow relative to siblings */
flex-shrink: 1; /* How much to shrink if container is too small */
flex-basis: 200px; /* Starting size before grow/shrink */
/* Override cross-axis alignment for this item only */
align-self: flex-end;
/* Override order (default: 0) */
order: 2;
}
/* === COMMON PATTERNS === */
/* Navbar: logo left, nav right */
.navbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 2rem;
}
/* Centered hero */
.hero {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
}
/* Card row that wraps */
.card-row {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
}
.card-row > .card {
flex: 1 1 280px; /* Cards grow but have a min width of 280px */
max-width: 400px;
}Step 4 — CSS Grid: Complete Guide
Grid is a two-dimensional layout system — it controls both rows and columns simultaneously. It's perfect for full page layouts, dashboards, and any design with rows AND columns.
/* === GRID CONTAINER === */
.grid {
display: grid;
/* Define columns: 3 equal columns */
grid-template-columns: 1fr 1fr 1fr;
/* Or with repeat() */
grid-template-columns: repeat(3, 1fr);
/* Or mixed sizes */
grid-template-columns: 200px 1fr 2fr;
/* Auto-fit: as many columns as fit, min 250px */
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
/* Define rows */
grid-template-rows: auto 1fr auto;
/* Gap between cells */
gap: 1.5rem; /* row-gap + column-gap */
row-gap: 1rem;
column-gap: 2rem;
/* Align all items inside their cells */
justify-items: stretch; /* start | end | center | stretch */
align-items: start; /* start | end | center | stretch */
}
/* === NAMED TEMPLATE AREAS (most readable approach) === */
.page {
display: grid;
grid-template-areas:
"header header header"
"sidebar content content"
"footer footer footer";
grid-template-columns: 240px 1fr 1fr;
grid-template-rows: auto 1fr auto;
min-height: 100vh;
}
.page-header { grid-area: header; }
.page-sidebar { grid-area: sidebar; }
.page-content { grid-area: content; }
.page-footer { grid-area: footer; }
/* === GRID ITEM PLACEMENT === */
.hero-banner {
/* Span 2 columns explicitly */
grid-column: 1 / 3; /* from line 1 to line 3 */
/* Or using span keyword */
grid-column: span 2;
grid-row: 1 / 2;
}
/* === RESPONSIVE CARD GRID (no media queries needed!) === */
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr));
gap: 1.5rem;
}auto-fit vs auto-fill
- auto-fill: creates as many columns as fit, leaving empty columns at the end.
- auto-fit: creates as many columns as fit, then COLLAPSES empty columns — items stretch to fill.
- For responsive card grids, auto-fit with minmax() is usually what you want.
Step 5 — Responsive Design
Responsive design ensures your layout looks great on every screen size. The mobile-first approach starts with the smallest screen and adds complexity as the screen grows.
/* === MOBILE FIRST === */
/* Start with mobile styles (no media query) */
.grid {
display: grid;
grid-template-columns: 1fr; /* Single column on mobile */
gap: 1rem;
}
/* Tablet: 640px+ */
@media (min-width: 40rem) {
.grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* Desktop: 1024px+ */
@media (min-width: 64rem) {
.grid {
grid-template-columns: repeat(3, 1fr);
}
}
/* === MODERN VIEWPORT UNITS === */
.hero {
min-height: 100svh; /* svh = small viewport height — excludes mobile browser chrome */
padding: 5dvh 5dvw; /* dvh/dvw = dynamic — updates as browser chrome shows/hides */
}
/* === CONTAINER QUERIES (the future of responsive design) === */
/* Apply to the parent that can resize */
.card-container {
container-type: inline-size;
container-name: card;
}
/* Query the CONTAINER width, not the viewport */
@container card (min-width: 400px) {
.card {
display: flex;
gap: 1rem;
}
.card__image {
width: 120px;
flex-shrink: 0;
}
}
/* === FLUID TYPOGRAPHY with clamp() === */
body {
/* Font size scales from 16px (mobile) to 20px (desktop) */
font-size: clamp(1rem, 0.5rem + 1.5vw, 1.25rem);
}Step 6 — CSS Animations & Transitions
Animations make interfaces feel alive and guide user attention. CSS handles most animation needs with no JavaScript required.
/* === TRANSITIONS: smooth state changes === */
.btn {
background: var(--color-primary);
/* property | duration | easing | delay */
transition: background-color 200ms ease-out,
transform 150ms ease,
box-shadow 200ms ease;
}
.btn:hover {
background: var(--color-primary-dark);
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(0,0,0,0.15);
}
.btn:active {
transform: translateY(0);
}
/* === KEYFRAME ANIMATIONS === */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* Usage */
.hero-title {
animation: fadeInUp 600ms ease-out both;
}
.hero-subtitle {
animation: fadeInUp 600ms ease-out 150ms both; /* 150ms delay */
}
.spinner {
width: 24px;
height: 24px;
border: 3px solid rgba(0,0,0,0.1);
border-top-color: var(--color-primary);
border-radius: 50%;
animation: spin 800ms linear infinite;
}
/* Skeleton loader */
.skeleton {
background: linear-gradient(90deg,
hsl(0,0%,85%) 25%,
hsl(0,0%,95%) 50%,
hsl(0,0%,85%) 75%
);
background-size: 200% 100%;
animation: skeleton-shimmer 1.5s ease-in-out infinite;
}
@keyframes skeleton-shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
/* === RESPECT USER PREFERENCES === */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}Step 7 — Modern CSS Functions
/* clamp(min, ideal, max) — fluid values that stay in range */
.title {
font-size: clamp(1.5rem, 4vw, 3rem); /* Never below 1.5rem or above 3rem */
padding: clamp(1rem, 5%, 3rem);
}
/* min() and max() */
.content {
width: min(90%, 1200px); /* Takes the smaller of 90vw or 1200px */
margin: 0 auto;
}
.sidebar {
width: max(200px, 20%); /* Takes the larger of 200px or 20% */
}
/* aspect-ratio */
.video-wrapper {
width: 100%;
aspect-ratio: 16 / 9; /* Maintains 16:9 regardless of width */
}
.avatar {
width: 48px;
aspect-ratio: 1; /* Perfect square */
border-radius: 50%;
}
/* Logical properties (start/end instead of left/right for RTL support) */
.card {
margin-inline: auto; /* margin-left + margin-right */
padding-block: 1rem; /* padding-top + padding-bottom */
padding-inline: 1.5rem; /* padding-left + padding-right */
border-inline-start: 4px solid var(--color-primary); /* left border in LTR */
}
/* gap works in Grid AND Flexbox */
.flex-nav {
display: flex;
gap: 0.5rem 1.5rem; /* row-gap column-gap */
}Step 8 — CSS Architecture (BEM)
As projects grow, CSS becomes hard to maintain. BEM (Block, Element, Modifier) is a naming convention that makes CSS predictable, modular, and team-friendly.
/* BEM: Block__Element--Modifier */
/* Block — a standalone, reusable component */
.card { ... }
/* Element — a part of the block (double underscore) */
.card__header { ... }
.card__body { ... }
.card__footer { ... }
.card__title { ... }
.card__image { ... }
/* Modifier — a variant or state (double dash) */
.card--featured { border: 2px solid gold; }
.card--compact { padding: 0.5rem; }
.card__button--primary { background: blue; }
.card__button--disabled { opacity: 0.5; cursor: not-allowed; }<article class="card card--featured">
<img class="card__image" src="thumb.jpg" alt="..." />
<div class="card__body">
<h2 class="card__title">Mathematics I</h2>
<p class="card__description">2024 examination paper with solutions.</p>
</div>
<footer class="card__footer">
<button class="card__button card__button--primary">View Paper</button>
</footer>
</article>Step 9 — Dark Mode
With CSS custom properties, implementing dark mode is elegant — swap the variable values, not all the individual rules.
:root {
color-scheme: light dark;
--bg: hsl(0, 0%, 100%);
--fg: hsl(220, 15%, 10%);
--surface: hsl(0, 0%, 97%);
--border: hsl(220, 13%, 88%);
}
/* Auto dark mode — respects OS preference */
@media (prefers-color-scheme: dark) {
:root {
--bg: hsl(222, 20%, 10%);
--fg: hsl(220, 15%, 92%);
--surface: hsl(222, 20%, 14%);
--border: hsl(222, 20%, 22%);
}
}
/* Manual toggle — class added by JS */
html.dark {
--bg: hsl(222, 20%, 10%);
--fg: hsl(220, 15%, 92%);
--surface: hsl(222, 20%, 14%);
--border: hsl(222, 20%, 22%);
}
body { background: var(--bg); color: var(--fg); }
.card { background: var(--surface); border: 1px solid var(--border); }Step 10 — CSS Performance Tips
Performance Best Practices
- will-change: transform — hints browser to promote element to its own compositor layer for smooth animations. Use sparingly.
- contain: layout style — tells browser this element's layout doesn't affect its siblings. Great for widgets.
- content-visibility: auto — skips rendering off-screen elements. Huge win for long pages.
- Use transform and opacity for animations — these run on the GPU and don't trigger layout.
- Avoid animating width, height, top, left — these trigger expensive layout recalculations.
- Load critical CSS inline in <head>; load non-critical CSS asynchronously.
/* GPU-accelerated animation — GOOD */
.slide-in {
animation: slideIn 300ms ease;
}
@keyframes slideIn {
from { transform: translateX(-100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
/* Slow animation — BAD (triggers layout on every frame) */
@keyframes badSlide {
from { left: -100px; }
to { left: 0; }
}
/* content-visibility for long lists */
.paper-card {
content-visibility: auto;
contain-intrinsic-size: auto 200px; /* estimated height for scrollbar accuracy */
}
/* Promote to own layer for smooth animation */
.modal {
will-change: transform, opacity;
}