Three ideas, one goal
These three terms get mixed up constantly. Here's the plain-English difference — and how they work together.
Modular
The page is assembled from reusable blocks — cards, sections, media objects — built on a shared grid and spacing scale. Compose, don't hand-craft.
Adaptive / responsive
The layout responds to the available space — reflowing columns, resizing type — so one build looks right from phone to desktop.
Contextual
The UI changes based on who's looking and how — input type, dark mode, motion preference, connection, even returning vs new users.
Modular layouts — build from blocks
A module is a self-contained block you reuse anywhere. Define them once and the page becomes a set of Lego pieces. The key tools are CSS Grid for the arrangement and a fixed scale for spacing and type.
Auto-fitting card modules
One rule lays out any number of cards and wraps them automatically — no media queries needed.
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 12px; /* fits as many 150px+ columns as space allows */
}
A bento grid — modules of different sizes
Give some modules span classes to break the uniform grid into a lively, magazine-like arrangement.
<div class="bento"> <div class="b-big">Featured</div> <div>A</div><div>B</div> <div class="b-wide">Wide</div> <div class="b-tall">Tall</div> <div>C</div><div>D</div> </div>
.bento { display: grid; grid-template-columns: repeat(4, 1fr); grid-auto-rows: 70px; gap: 10px; }
.b-wide { grid-column: span 2; }
.b-tall { grid-row: span 2; }
.b-big { grid-column: span 2; grid-row: span 2; }
A modular scale keeps it consistent
Pick spacing and type from a fixed set of steps — not random pixels. Every module then lines up.
:root {
--ratio: 1.25; /* a "major third" scale */
--step-0: 1rem;
--step-1: calc(var(--step-0) * var(--ratio)); /* 1.25rem */
--step-2: calc(var(--step-1) * var(--ratio)); /* 1.563rem */
--step-3: calc(var(--step-2) * var(--ratio)); /* 1.953rem */
}
h3 { font-size: var(--step-1); } /* every size comes from the scale */
Adaptive UI — respond to the space
Responsive layouts react to the viewport. Modern CSS goes further: components can react to their own container, and type can scale smoothly instead of jumping at breakpoints.
Container queries — components adapt to their box
Drag the resize handle at the bottom-right of the box. The same card flips from stacked to side-by-side based on the container's width — not the screen's.
I adapt to my container
Narrow = stacked. Wide enough = image beside text. Resize the dashed box →
.wrap { container-type: inline-size; } /* make .wrap a query container */
.card { display: grid; gap: 12px; } /* stacked by default */
@container (min-width: 420px) { /* when the CONTAINER is wide enough */
.card { grid-template-columns: 160px 1fr; align-items: center; }
}
Fluid type with clamp()
Type that scales smoothly between a min and max as the window resizes — no breakpoint jumps. Resize your browser to see it move.
h1 { font-size: clamp(1.4rem, 5vw, 3rem); }
/* ↑min ↑grows ↑max
never smaller than 1.4rem, never bigger than 3rem,
fluid in between (5% of viewport width) */
Progressive disclosure
Show the essentials; reveal detail on demand. Adapts the amount of UI to what the user needs right now. (Click to expand.)
What's included in the plan?
Unlimited projects, a custom domain, and priority support. Native <details> needs zero JavaScript — the browser handles the toggle and it's keyboard-accessible by default.
<details> <summary>What's included in the plan?</summary> <p>Unlimited projects, a custom domain, and priority support.</p> </details> <!-- collapsible, accessible, no JS required -->
Contextual UI — respond to the person
The most thoughtful interfaces adjust to the situation, not just the screen size. CSS media queries can read a surprising amount of context — and you respond to it with the same tools you already know.
Color scheme
prefers-color-scheme tells you if the user wants dark mode — honor it automatically.
Motion
prefers-reduced-motion lets people who get dizzy turn off your animations.
Input type
pointer: coarse means a finger — make tap targets bigger; fine means a mouse.
Data & connection
prefers-reduced-data & the Network API let you serve lighter pages on slow or metered connections.
The user
New vs returning, signed-in vs guest, their saved preferences — show the UI that fits where they are.
Locale & time
Language, currency, date format, even a light/dark greeting by time of day — small touches that feel personal.
Live: this card respects your OS color scheme
If your device is set to dark mode, the card below is dark; in light mode it's light — with no toggle and no JavaScript.
.card { background: #f8fafc; color: #1d1d1f; }
@media (prefers-color-scheme: dark) {
.card { background: #1e293b; color: #e2e8f0; }
}
/* bigger touch targets for finger input */
@media (pointer: coarse) {
.btn { min-height: 48px; padding: 14px 22px; }
}
How to build this way
Define your scale & grid
Lock in a spacing scale, a type scale, and a base grid. Everything else snaps to these.
Build modules, not pages
Create reusable blocks — card, media object, section, hero — styled only from your tokens.
Compose the page
Assemble screens from modules on the grid. New layouts become rearranging blocks, not rewriting CSS.
Make modules adapt
Use auto-fit/auto-fill, container queries, and clamp() so each block flexes on its own.
Layer in context
Honor color scheme, motion, and input preferences. Add personalization where it genuinely helps.
Characteristics & components
The signals of a modular, adaptive build:
Responsive layouts Reusable sections Mobile-first
Components to build
A responsive grid, an adaptive card, a flexible nav, and a mobile menu — live first, then the code.
Image beside text when there's room; stacks when narrow.
/* responsive grid — wraps with no media queries */
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)); gap: 8px; }
/* adaptive card — flexes image beside text, stacks when narrow */
.card { display: flex; flex-wrap: wrap; gap: 12px; }
.card .text { flex: 1; min-width: 120px; }
/* flexible nav + mobile menu button */
.nav { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; }
.nav .links { display: flex; gap: 14px; }
.nav .burger { display: none; }
@media (max-width: 600px) {
.nav .links { display: none; } /* hide links on small screens */
.nav .burger { display: block; } /* show the hamburger */
}
<!-- responsive grid -->
<div class="grid">
<div>1</div><div>2</div><div>3</div><div>4</div>
</div>
<!-- adaptive card -->
<div class="card">
<div class="thumb"></div>
<div class="text">
<strong>Adaptive card</strong>
<p>Image beside text when there's room; stacks when narrow.</p>
</div>
</div>
<!-- flexible nav + mobile menu -->
<nav class="nav">
<strong>Flex</strong>
<div class="links"><a>Home</a><a>Docs</a></div>
<button class="burger" aria-label="Menu">☰</button>
</nav>
Do & don't
Do
- Build reusable modules on a shared grid
- Size everything from a spacing & type scale
- Use
auto-fit+ container queries to skip breakpoints - Honor
prefers-color-scheme&reduced-motion - Make touch targets bigger on
pointer: coarse - Reveal detail progressively
Don't
- Hand-craft every page from scratch
- Use random one-off pixel sizes
- Pile up dozens of fragile breakpoints
- Force one theme on everyone
- Ship desktop-only tap targets to phones
- Dump every option on screen at once
The takeaway
Modular = build once from reusable blocks on a shared scale and grid. Adaptive
= let those blocks respond to the space with auto-fit, container queries, and clamp().
Contextual = respond to the person with prefers-color-scheme,
reduced-motion, pointer, and smart personalization. Together they turn one careful
build into an interface that feels right everywhere, for everyone.