On this page

Responsive design

12 min read TextCh. 3 — Modern Layout

What is responsive design?

Responsive design is the strategy of building websites that adapt to any screen size: from 320px phones to 4K monitors. Instead of creating separate versions, a single site adjusts fluidly.

The three pillars of responsive design are:

  1. Viewport meta tag (essential)
  2. Media queries (breakpoints)
  3. Fluid units and functions (clamp, vw, %)

The viewport meta tag

Without this tag, mobile browsers render the page as if it were a desktop and scale it down. It is mandatory:

<meta name="viewport" content="width=device-width, initial-scale=1">

Mobile first

The mobile first strategy consists of writing base styles for small screens and using media queries to add complexity on larger screens.

/* Base: mobile (one column) */
.grid { display: grid; grid-template-columns: 1fr; }

/* Tablet: two columns */
@media (width >= 768px) {
  .grid { grid-template-columns: repeat(2, 1fr); }
}

/* Desktop: three columns */
@media (width >= 1024px) {
  .grid { grid-template-columns: repeat(3, 1fr); }
}

It is easier to add complexity for larger screens than to remove complexity for smaller screens.

Modern media queries

The modern syntax uses comparison operators instead of min-width and max-width:

/* Classic syntax */
@media (min-width: 768px) { }

/* Modern syntax (recommended) */
@media (width >= 768px) { }

/* Range */
@media (768px <= width < 1024px) { }
Name Width Typical use
sm 640px Large phones
md 768px Tablets
lg 1024px Laptops
xl 1280px Desktops
2xl 1536px Large screens

You do not have to use all of them. Use only the ones your content needs.

Fluid units

Percentages and viewport units

Unit Relative to
% Parent element
vw 1% of the viewport width
vh 1% of the viewport height
dvh Dynamic height (respects mobile address bar)
svh Smallest possible height
lvh Largest possible height

The clamp() function

clamp(minimum, ideal, maximum) is the most powerful tool for fluid values:

/* Typography that scales with the screen */
h1 { font-size: clamp(1.5rem, 4vw, 3.5rem); }

/* Readable content width */
.content { max-width: clamp(45ch, 80%, 75ch); }

/* Adaptive padding */
.section { padding: clamp(1rem, 5vw, 4rem); }

With clamp(), you can eliminate many media queries.

Responsive images

Images must adapt to their container without overflowing:

img {
  max-width: 100%;
  height: auto;
  display: block;
}

For more control, use the HTML srcset attribute with different resolutions:

<img
  src="photo-800.jpg"
  srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w"
  sizes="(width >= 1024px) 33vw, (width >= 768px) 50vw, 100vw"
  alt="Photo description"
>

Container queries

Media queries respond to the viewport size. Container queries respond to the parent container size. This makes components truly reusable.

.wrapper {
  container-type: inline-size;
}

@container (width >= 400px) {
  .card {
    display: flex;
    gap: 1rem;
  }
}

With container queries, a card component adapts based on where you place it: in a narrow sidebar it appears vertical, in a wide area it appears horizontal. Without changing anything in the component.

Responsive layouts without media queries

Combining Grid and Flexbox tools, you can create layouts that adapt automatically:

/* Auto-fill + minmax = automatic columns */
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 1rem;
}

/* Flexbox wrap with minimum basis */
.cards {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}
.card { flex: 1 1 300px; }

Your site now adapts to any screen. Next, we will refine typography, a crucial element for readability and aesthetics.

Practice

  1. Implement a mobile-first layout: Create an article grid that shows 1 column on mobile, 2 on tablet (768px), and 3 on desktop (1024px) using media queries with the modern (width >= ...) syntax.
  2. Use clamp() for fluid typography: Apply font-size: clamp(...) to a heading and a paragraph so they scale smoothly between small and large screens without media queries.
  3. Create a component with container queries: Wrap a card in a container with container-type: inline-size and use @container to change its layout based on the container width.
Mobile first
Always write base styles for mobile and add complexity with @media (width >= ...) for larger screens. It is easier to add complexity than to remove it.
Do not forget the viewport
Without the viewport meta tag in your HTML, media queries will not work correctly on mobile devices. Make sure to include <meta name="viewport" content="width=device-width, initial-scale=1">.
/* Mobile first: base styles for mobile */
.articles-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.5rem;
  padding: 1rem;
}

/* Tablet: 2 columns */
@media (width >= 768px) {
  .articles-grid {
    grid-template-columns: repeat(2, 1fr);
    padding: 2rem;
  }
}

/* Desktop: 3 columns with sidebar */
@media (width >= 1024px) {
  .layout {
    display: grid;
    grid-template-columns: 1fr 300px;
    gap: 2rem;
  }

  .articles-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* Fluid typography with clamp() */
.title {
  font-size: clamp(1.5rem, 4vw, 3rem);
  line-height: 1.2;
}

.content {
  max-width: clamp(45ch, 60%, 75ch);
  margin-inline: auto;
}
/* Container queries: respond to the container, not the viewport */
.card-wrapper {
  container-type: inline-size;
  container-name: card;
}

.card {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
  padding: 1rem;
}

/* When the CONTAINER is 400px+ */
@container card (width >= 400px) {
  .card {
    grid-template-columns: 150px 1fr;
    align-items: center;
  }
}

@container card (width >= 600px) {
  .card {
    grid-template-columns: 200px 1fr auto;
  }
}