Skip to content
AgileLogics
7 min read

Responsive Design and Media Queries

Prerequisites: CSS Grid basics

Responsive design means one page that adapts to any screen. It rests on three things:

  1. The viewport meta tag in the HTML (<meta name="viewport" content="width=device-width, initial-scale=1">).
  2. Fluid layouts — Flexbox, Grid, percentages, max-width, and images with max-width: 100%.
  3. Media queries for the changes fluid layout can't handle.

Media queries

.nav { display: flex; flex-direction: column; }

@media (width >= 48rem) {
  .nav { flex-direction: row; }
}

@media (width >= 48rem) and (width < 64rem) {
  .sidebar { display: none; }
}

@media print {
  .nav, .ads { display: none; }
  a[href^="http"]::after { content: " (" attr(href) ")"; }
}
  • The range syntax (width >= 48rem) works in all current browsers. You'll also see the older (min-width: 48rem) form, which means the same thing.
  • Media queries in em/rem respect the user's font size setting.
  • print styles hide navigation and show link URLs on paper.

Mobile first

Write the base styles for small screens, then add complexity for larger ones with min-width/>= queries. Small-screen styles are usually simpler, so you override less.

Choosing breakpoints

Don't target specific devices — there are thousands. Resize the browser and add a breakpoint where your content starts to look wrong. Often two or three are enough, and repeat(auto-fit, minmax(...)) grids, flex-wrap, and clamp() remove the need for many.

User preference queries

@media (prefers-color-scheme: dark) {
  :root { --bg: #111; --text: #eee; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

@media (prefers-contrast: more) {
  :root { --border: #000; }
}

@media (hover: hover) and (pointer: fine) {
  .card:hover { transform: translateY(-2px); }
}
  • prefers-color-scheme follows the operating system's dark mode.
  • prefers-reduced-motion — people with vestibular disorders or migraines turn off animations. Respect it.
  • hover / pointer — detect a mouse versus a touch screen, instead of guessing from screen width.
  • forced-colors: active detects Windows High Contrast mode.

Testing a media query from JavaScript

<script>
  console.log("wide:", matchMedia("(width >= 48rem)").matches);
  console.log("print:", matchMedia("print").matches);
  console.log("reduced motion:", matchMedia("(prefers-reduced-motion: reduce)").matches);
</script>
wide: true
print: false
reduced motion: false

(Run in a desktop browser window about 1280 pixels wide, with default settings.)

Practice

Make .cards one column by default, two columns from 40rem, and three from 64rem, using Grid.

Show solution
.cards {
  display: grid;
  gap: 1rem;
}

@media (width >= 40rem) {
  .cards { grid-template-columns: repeat(2, 1fr); }
}

@media (width >= 64rem) {
  .cards { grid-template-columns: repeat(3, 1fr); }
}

Or with no media queries at all: grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));