Responsive design stopped being about three breakpoints a long time ago. It is about adapting to the container, the device's capabilities and the user's preferences at once, while never shifting layout. Interviewers test it with concrete scenarios: a card that must work in a sidebar and a hero, a dashboard on a phone, a user who has reduced motion switched on.
min-width queries; use clamp(min, preferred, max) with viewport or container units for type and spacing so values scale continuously (font-size: clamp(1rem, 0.9rem + 0.5vw, 1.25rem)); min()/max() for constrained widths (width: min(100%, 72ch)).@container sizes it by its parent, cqi units scale inside it. Media queries remain for page-level layout and for user preferences.dvh (dynamic, accounts for the URL bar), svh/lvh; 100vh overflows on mobile Safari. env(safe-area-inset-*) with viewport-fit=cover keeps content out of notches and home indicators.@media (hover: hover) before adding hover-only affordances, (pointer: coarse) for larger targets; WCAG 2.2 requires targets of at least 24×24 CSS pixels (2.5.8, AA) with 44×44 as the comfortable mobile norm; touch-action to control gestures; no hover-only disclosure of essential content.prefers-reduced-motion: reduce (disable or shorten animations; in Vue use VueUse usePreferredReducedMotion, in Nuxt experimental.viewTransition: true already respects it), prefers-color-scheme with color-scheme and light-dark(), prefers-contrast: more, forced-colors: active (Windows High Contrast; use currentColor and system colours), prefers-reduced-data.width/height or aspect-ratio, reserve space for late content (skeletons of the final size), load fonts with font-display and metric-compatible fallbacks (@nuxt/fonts), avoid injecting banners above content; CLS is a Core Web Vital.srcset + sizes so the browser picks a resolution for the layout width, <picture> for art direction and format fallbacks, loading="lazy" below the fold, decoding="async", fetchpriority="high" for the LCP image; @nuxt/image generates all of it from sizes="100vw md:50vw".max-inline-size: 65ch), text-wrap: balance for headings and pretty for body, relative units (rem) so user zoom and font settings work, line-height unitless.@media (min-width) still, plus the Viewport Segments API for dual screens where relevant; wide screens need a max content width, not a stretched layout.@media print to hide chrome, expand links (a[href]::after { content: " (" attr(href) ")" }), avoid page breaks inside cards (break-inside: avoid); often forgotten and cheap.prefers-* emulation in the Rendering panel, Playwright emulateMedia({ reducedMotion: 'reduce', colorScheme: 'dark' }) and viewport matrices in CI.A card that adapts to its container, the user's preferences and the device, with no layout shift:
:root {
color-scheme: light dark;
--step-0: clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
--step-2: clamp(1.5rem, 1.2rem + 1.5vw, 2.25rem);
--gutter: clamp(1rem, 4vw, 2rem);
}
.page {
min-block-size: 100dvh; /* not 100vh */
padding: var(--gutter) max(var(--gutter), env(safe-area-inset-right)) var(--gutter) max(var(--gutter), env(safe-area-inset-left));
max-inline-size: 80rem;
margin-inline: auto;
}
.card {
container-type: inline-size;
display: grid;
gap: 1rem;
img { inline-size: 100%; aspect-ratio: 16 / 9; object-fit: cover; } /* space reserved: no CLS */
h2 { font-size: var(--step-2); text-wrap: balance; }
p { max-inline-size: 65ch; text-wrap: pretty; }
@container (min-width: 36rem) { grid-template-columns: 2fr 3fr; align-items: start; }
}
/* affordances only where they make sense */
@media (hover: hover) and (pointer: fine) {
.card a:hover { text-decoration-thickness: 2px; }
}
@media (pointer: coarse) {
.card button { min-block-size: 44px; min-inline-size: 44px; }
}
/* preferences */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; }
}
@media (prefers-contrast: more) { .card { border: 2px solid currentColor; } }
@media (forced-colors: active) { .card { border: 1px solid CanvasText; } }
@media print {
nav, .card button { display: none; }
.card { break-inside: avoid; }
a[href^="http"]::after { content: " (" attr(href) ")"; }
}
Responsive images the Nuxt way, and motion that respects the user in Vue:
<script setup lang="ts">
const reduced = usePreferredReducedMotion() // 'reduce' | 'no-preference'
</script>
<template>
<NuxtPicture
:src="image"
sizes="100vw md:80vw xl:1200px"
format="avif,webp"
width="1600"
height="900"
:img-attrs="{ fetchpriority: 'high', decoding: 'async' }"
/>
<Transition :name="reduced === 'reduce' ? undefined : 'fade-up'">
<h1 v-if="ready">{{ title }}</h1>
</Transition>
</template>
min-height: 100vh is taller than the visible area while the mobile browser's toolbar is shown, so the bottom of the page is hidden until the user scrolls. 100dvh tracks the visible viewport; svh is the safe minimum when you must avoid resizing during scroll.
Menus that open only on hover exclude touch and keyboard users and fail WCAG. Open on click or focus, add @media (hover: hover) for the hover enhancement, and make the trigger a real <button> with aria-expanded.
@container and drop it into a 20rem sidebar and a 60rem main column; no media queries allowed.aspect-ratio and dimensions to every image and embed, re-measure."Mobile-first and fluid: clamp() for type and spacing so values scale continuously, min() and max() for constrained widths, and container queries so a component adapts to the space it is given rather than to the viewport, which media queries never captured. Then I adapt to the device and the person: dvh and safe-area insets on phones, pointer and hover queries before adding hover-only affordances, 24-pixel minimum targets per WCAG 2.2 and 44 on touch, and preference queries for reduced motion, colour scheme, contrast and forced colours. And I keep the layout stable: dimensions or aspect ratios on every image, metric-compatible font fallbacks, reserved space for late content, because CLS is a Core Web Vital and a shifting layout is the most visible defect a user meets."
Nothing yet. Notes filed here show up on this page and the section overview.
CSS architecture and Tailwind v4
Ownership models for styles — scoped SFC styles, CSS Modules, utility-first with Tailwind v4's CSS-first configuration, design tokens as custom properties, theming and dark mode, how Nuxt UI composes Tailwind variants with app.config tokens, and what happened to BEM and CSS-in-JS.
Accessibility fundamentals
WCAG 2.2 and its new criteria, the European Accessibility Act, semantic HTML first, the rules of ARIA, keyboard and focus management, forms and errors, colour and motion, screen reader testing, and the tooling that makes accessibility verifiable.