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.csscss
/* 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.
layers.csscss
@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.

variables.csscss
: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.

flexbox.csscss
/* === 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.csscss
/* === 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.

responsive.csscss
/* === 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.

animations.csscss
/* === 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

modern-functions.csscss
/* 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.csscss
/* 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; }
bem-example.htmlhtml
<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.

dark-mode.csscss
: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.
performance.csscss
/* 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;
}