030: Responsive Design
Learning outcomes
By the end, you can explain responsive design as a strategy; configure the viewport; create fluid wrappers, media, typography, Flexbox, and Grid; distinguish intrinsic responsiveness from breakpoints; prioritize content mobile-first; and test beyond device presets.
Prerequisites and retrieval
Before starting, retrieve the ideas that make responsive CSS work: fluid constraints, normal flow, Flexbox wrapping, and Grid's auto-fit/minmax(). Look back at the portfolio and identify which rules already adapt without a media query. That is the right starting point. Responsive work is built from resilient defaults, not from a catalogue of phone models.
Terminology
- Responsive web design: Design approach letting layout adapt fluidly across devices, viewports, and user contexts. — Source: MDN: Responsive design
- Viewport: The browser area used to lay out the document, configured via viewport meta. — Source: MDN: Viewport meta tag
- Mobile-first: Start with simple narrow-space defaults, then enhance when space supports it (course term).
- Fluid layout: Sizes adapting continuously to available space using %/vw/clamp instead of fixed widths. — Source: MDN: Responsive design
- Intrinsic layout: Content and available space driving layout outcomes (auto-fit/minmax). — Source: CSS Sizing 3: Intrinsic sizes
- Breakpoint: Condition where design needs a discrete change (course term).
- Reflow: Content reflowing to remain readable without page-level two-dimensional scrolling (WCAG 1.4.10). — Source: WCAG 2.2: Reflow
- Responsive media: Images/video scaling with their container and ideally switching resources (srcset/sizes). — Source: MDN: Responsive images
- Container query (
@container): "A conditional rule that applies styles based on the size of a containing element, not the viewport." — Source: CSS Containment Module Level 3: Container Queries — deferred to 038; this lesson focuses on viewport and intrinsic responsiveness - Intrinsic sizing vs extrinsic: "Intrinsic sizing is driven by content; extrinsic sizing is imposed from outside (fixed width)." — Source: CSS Sizing Level 3: Intrinsic vs Extrinsic
Mental model: a resilient system, not three screenshots
A responsive design is not a set of three layouts that happen to look good in screenshots. It is a set of relationships that continues to work as the window narrows or widens, the user zooms, default fonts grow, translations expand, focus moves by keyboard, motion is reduced, orientation changes, or content arrives in an unexpected shape. “Desktop, tablet, mobile” are useful samples of a system, not the system itself.
Build that system in this order:
- Semantic source order and normal flow.
- Fluid sizes with useful min/max constraints.
- Flexible media and readable measure.
- Intrinsic Flexbox/Grid behavior.
- Media queries only where content demonstrates a need.
Mobile-first does not mean designing only for phones. Narrow defaults make content priorities visible and usually lead to simpler CSS. Once more space is available, wider conditions can add columns, spacing, or alignment instead of trying to undo rigid desktop assumptions.
Beginner example: responsive portfolio without queries
Start by ensuring the document head contains the viewport configuration:
<meta name="viewport" content="width=device-width, initial-scale=1">
Without this element, a mobile browser may lay the page out in a wider layout viewport and then scale the result down. That can make otherwise reasonable CSS appear tiny. Do not add maximum-scale=1 or user-scalable=no; those settings take zoom away from users who need it.
html { box-sizing: border-box; }
*, *::before, *::after { box-sizing: inherit; }
body {
margin: 0;
color: rgb(30 41 59);
background: rgb(248 250 252);
font-family: system-ui, sans-serif;
line-height: 1.6;
}
.site-shell {
inline-size: min(100% - 2rem, 70rem);
margin-inline: auto;
}
img,
svg,
video {
display: block;
max-inline-size: 100%;
block-size: auto;
}
.hero {
padding-block: clamp(2.5rem, 8vw, 7rem);
}
.hero h1 {
max-inline-size: 14ch;
font-size: clamp(2.25rem, 1.6rem + 3vw, 5rem);
line-height: 1.05;
}
.project-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
gap: clamp(1rem, 3vw, 1.5rem);
}
The shell leaves 2rem for its edges and stops growing at 70rem. Images cannot become wider than their containers. clamp() keeps hero spacing and display type fluid while still giving both a floor and a ceiling. The grid creates as many useful tracks as the available space permits. Drag the viewport one pixel at a time and you will see continuous changes: cards rearrange when their minimum size requires it, not when the browser crosses a device-name boundary.
Keep body text at a stable, readable size. Fluid display text can be useful, but bound it with clamp() rather than relying on viewport units alone.
Intermediate example: responsive image resources
CSS can make an image occupy less space, but it cannot undo bytes that the browser has already downloaded. Let HTML describe several image resources so the browser can choose one that fits the rendered slot:
<img
src="images/library-800.jpg"
srcset="images/library-480.jpg 480w,
images/library-800.jpg 800w,
images/library-1280.jpg 1280w"
sizes="(min-width: 48rem) 33vw, 100vw"
width="1280"
height="720"
loading="lazy"
alt="Library finder showing search results and opening hours">
srcset lists candidate intrinsic widths. sizes gives the browser an estimate of the rendered slot: about one-third of the viewport in the wider grid and the full viewport in the narrow layout. The final choice also considers density, cache state, and other browser factors. src is the fallback. width and height reserve the aspect ratio before the image arrives, while loading="lazy" can defer project images below the fold. Do not lazy-load the likely largest above-the-fold hero image, because delaying that resource can delay the main content.
Use <picture> for art direction when the composition genuinely needs a different crop at a condition. If the only difference is resolution, srcset is the more appropriate tool.
Content and component decisions
Responsive design includes editorial decisions, not just CSS mechanics. Navigation labels should stay concise without becoming cryptic. Project cards need to cope with different title lengths, descriptions, and visible actions. Narrow space is not a reason to hide essential content. If the priority order needs to change, put that order in the HTML instead of using CSS order to create a different focus or reading sequence.
Flexbox is often enough for a header that should wrap naturally:
.site-header {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 1rem 2rem;
}
.nav-list { display: flex; flex-wrap: wrap; gap: 0.5rem; }
For a small portfolio, this may be all the navigation transformation needed. A JavaScript disclosure menu introduces keyboard handling, focus management, accessible naming, and state to maintain. Do not add one merely because a mobile screenshot traditionally contains a hamburger icon.
Optional advanced example: user preference
If the portfolio uses motion that is not necessary for understanding its content, respect a user's reduced-motion preference:
.project-card {
transition: transform 180ms ease, box-shadow 180ms ease;
}
.project-card:hover { transform: translateY(-0.2rem); }
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
scroll-behavior: auto !important;
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
The simplest choice is not to add nonessential motion. When motion is present, reduce it according to the user's preference, and never make necessary content comprehension depend on animation.
Mistakes, debugging, and DevTools
These failures are common because they make a page look correct at one width while the underlying relationships are brittle:
- Designing fixed desktop widths and shrinking afterward.
- Treating preset device widths as complete testing.
- Disabling zoom in the viewport meta element.
- Hiding important content on narrow screens.
- Using
100vwinside a page and causing horizontal overflow. - Sending one huge image despite rendering small thumbnails.
- Using viewport-only type sizes with no minimum/maximum.
- Rearranging visual order independently from focus/read order.
- Adding breakpoints where wrapping and intrinsic tracks already solve the problem.
Responsive mode in DevTools is useful, but do not stop at its named device presets. Resize an ordinary browser window too. Test at 320 CSS pixels, at intermediate widths, in landscape, at 200% and 400% zoom, with a larger default font, long words, sparse and dense cards, keyboard navigation, and a slow network. Find the first width where the content becomes cramped or the relationships stop working. That observation, rather than a familiar device width, is what can justify a breakpoint.
Accessibility and performance
WCAG Reflow is concerned with whether content remains usable at an equivalent 320 CSS pixels wide: there should be no loss of content and no page-level two-dimensional scrolling, apart from genuinely two-dimensional content. Text must remain usable at 200% resizing. Controls need adequate size and spacing, and focus must not be clipped. Unless one orientation is genuinely essential, the layout should work in both orientations.
Responsive performance is about delivering resources in proportion to the space and context in which they will be used. Compress images, use srcset and sizes, reserve dimensions, avoid unnecessary fonts, and make critical above-the-fold content discoverable promptly. A small viewport does not imply a fast connection, so test with throttled network and CPU conditions instead of treating mobile as a performance guarantee.
Deep dive: responsive design has several independent axes
Viewport width is only one input to a responsive system. Test changes in:
- available inline size;
- text size and zoom;
- content length and localization;
- input method;
- user motion/contrast/color preferences;
- image/network constraints;
- orientation;
- component container size.
A layout that survives at 375px but breaks when the page is zoomed to 200% on a 1280px viewport is not robust. The apparent width has not been the only thing that changed; text, available space, and the user's way of interacting with the page changed too.
Deep dive: intrinsic responsiveness before queries
Prefer mechanisms that respond continuously before adding discrete conditions:
.shell {
width: min(100% - 2rem, 72rem);
margin-inline: auto;
}
.cards {
display: grid;
grid-template-columns:
repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
gap: 1rem;
}
.hero h1 {
font-size: clamp(2rem, 1.2rem + 4vw, 4.5rem);
}
Add a media query only when an actual relationship needs a discrete change. If wrapping, a minimum track size, or a fluid constraint already preserves the relationship, another breakpoint adds complexity without solving a real problem.
Worked example: responsive media card
The markup describes the content and the available image candidates:
<article class="media-card">
<img
class="media-card__image"
src="course-640.jpg"
srcset="
course-320.jpg 320w,
course-640.jpg 640w,
course-960.jpg 960w"
sizes="(min-width: 48rem) 40vw, 100vw"
alt="Laptop showing a CSS layout exercise"
width="960"
height="640"
>
<div class="media-card__content">
<h2>CSS Layout</h2>
<p>Practice Flexbox and Grid with real constraints.</p>
</div>
</article>
The CSS controls how the card occupies space and how the image is cropped:
.media-card {
display: grid;
gap: 1rem;
}
.media-card__image {
width: 100%;
aspect-ratio: 3 / 2;
object-fit: cover;
border-radius: 0.75rem;
}
HTML chooses an image resource appropriate to the slot. CSS controls layout and crop. Keeping those jobs distinct is the practical meaning of responsive design as cooperation between markup and styling.
Deep dive: touch and pointer assumptions
Never make an essential action appear only on hover:
.card__actions {
opacity: 1;
}
A hover effect can be an enhancement for devices that actually support hover and have a fine pointer:
@media (hover: hover) and (pointer: fine) {
.card {
transition: transform 160ms ease;
}
.card:hover {
transform: translateY(-2px);
}
}
The card remains complete and its actions remain available without hover, which matters for touch, keyboard, and other input methods.
Worked example: responsive table strategy
Some tables are genuinely two-dimensional. In that case, horizontal scrolling can preserve the relationships between rows and columns:
<div class="table-scroll" tabindex="0">
<table>
...
</table>
</div>
.table-scroll {
overflow-x: auto;
max-inline-size: 100%;
}
Do not turn tabular data into unrelated cards just to eliminate scrolling when the row and column relationships carry meaning. A responsive solution should preserve the information architecture, not only avoid a scrollbar.
If the wrapper is keyboard-focusable to make scrolling discoverable, provide an accessible label when one is needed and make sure its focus treatment is visible.
Deep dive: localization stress test
Short English copy can hide sizing assumptions. Try each of these inputs:
- English button:
Save - German-like expansion:
Änderungen speichern - long user-generated name;
- Arabic/Hebrew direction;
- 200% text size.
Avoid fixed widths that assume every label will fit the English version:
/* fragile */
.button {
width: 100px;
}
Let the content determine the inline size while still giving the control a usable minimum block size:
.button {
min-block-size: 2.75rem;
padding-inline: 1rem;
}
Responsive test matrix
For each major component, record the result of these checks:
| Test | Question |
|---|---|
| 320 CSS px | Does content reflow without page-level two-axis scrolling? |
| 200% zoom | Does content remain operable and readable? |
| long content | Does the component grow/wrap instead of clipping? |
| keyboard | Are focus order and indicators usable? |
| touch | Are controls large enough and not hover-dependent? |
| slow image/font | Is layout stable while assets load? |
| reduced motion | Are non-essential animations reduced? |
| high contrast | Are borders/focus states still perceivable? |
This is a product test, not a final exercise in matching one screenshot. The matrix gives you observations you can act on and repeat after a fix.
Tiered exercises
Checkpoint: build a responsive test matrix
Create rows for 320px, an arbitrary middle width, a wide window, 200% text, 400% zoom, landscape, keyboard-only input, and a slow connection. Create columns for navigation, hero, cards, images, form controls, focus, and page overflow. Record pass/fail observations rather than “looks good.” Fix failures in the simplest shared rule before adding a breakpoint.
Content variation belongs in the matrix too. Test zero optional cards, one card, many cards, a three-line heading, a long email address, a missing image, and translated navigation. Intrinsic layout often handles these cases automatically. When it does not, identify whether the failure comes from content breaking, a minimum size, source order, or a genuinely discrete relationship change.
Responsive design is complete only when resource behavior matches visual behavior. Use the Network panel to compare image candidates at narrow and wide slots and at different pixel densities. A visually fluid 200px thumbnail that downloads a multi-megabyte 3000px source is responsive in layout but not in delivery.
Keep a short decision log during testing: observation, responsible rule, smallest fix, and regression checks. For example, “At 37rem the third navigation label wraps alone; wrapping remains readable, so no breakpoint is needed.” Not every visual change is a failure. This record prevents later contributors from adding device-specific patches for behavior that was intentionally accepted.
Foundation: Add the viewport meta element, fluid shell, flexible media, and readable prose width. Verify no horizontal page scrollbar at 320px.
Core: Build an intrinsic auto-fitting project grid and bounded hero type/spacing. Test continuously rather than at three presets.
Stretch: Create responsive srcset for one project image and document its likely rendered slot. Add reduced-motion handling only if the portfolio includes nonessential transitions or animation.
<meta name="viewport" content="width=device-width, initial-scale=1">
<img src="images/library-800.jpg"
srcset="images/library-480.jpg 480w, images/library-800.jpg 800w, images/library-1280.jpg 1280w"
sizes="(min-width: 48rem) 33vw, 100vw"
width="1280" height="720" loading="lazy"
alt="Library finder showing search results and opening hours">
.site-shell { inline-size: min(100% - 2rem, 70rem); margin-inline: auto; }
img, svg, video { display: block; max-inline-size: 100%; block-size: auto; }
.hero { padding-block: clamp(2.5rem, 8vw, 7rem); }
.hero h1 { max-inline-size: 14ch; font-size: clamp(2.25rem, 1.6rem + 3vw, 5rem); }
.project-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr)); gap: clamp(1rem, 3vw, 1.5rem); }
Recap and exit questions
Responsive design is a layered strategy: semantic order, fluid constraints, flexible resources, intrinsic layout, then justified conditional changes. Test variation in content and user context, not just a handful of device widths.
- Why is responsive design broader than media queries?
- What does the viewport meta element change?
- How does an auto-fit Grid respond without breakpoints?
- Why are
srcsetandsizesperformance features? - What observation justifies a breakpoint?
