CSSIntermediate

Responsive Web Design: Mobile-First and Media Queries

Make one page work on phones, tablets and desktops. Learn the viewport tag, mobile-first CSS, media queries, breakpoints and flexible images by example.

All CSS lessons

What you will learn

People visit your site on 5-inch phones, tablets, laptops and wide monitors. Responsive design means writing one page that reshapes itself to suit every one of those screens, so nobody has to pinch and zoom or scroll sideways. In this lesson you will learn the one line of HTML every responsive page needs, why you should write CSS mobile-first, how media queries work, how to choose breakpoints, how to make images flexible, and you will see all of it working in one real page.

The three ingredients

Responsive design is built from three things:

  1. Flexible layouts: widths in %, fr, max-width, Flexbox and Grid instead of fixed pixel widths. You have already learned most of this.
  2. Flexible media: images and videos that shrink to fit their container.
  3. Media queries: CSS rules that apply only on certain screen sizes or conditions.

The viewport meta tag

Phones have a trick: if a page does not say it is mobile-friendly, the phone pretends the screen is about 980px wide and then shrinks everything to fit. Your carefully designed phone layout never appears. The fix is one line in the <head> of your HTML:

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

It tells the browser: “use the real width of the device, and start at normal zoom”. Without it, media queries do not behave as expected on phones. Add it to every page, right after <meta charset="UTF-8">.

The HTML we will style

<header class="site-header">
  <a href="#home" class="logo">CodeNest</a>
  <nav class="main-nav">
    <a href="#html" class="nav-link">HTML</a>
    <a href="#css" class="nav-link">CSS</a>
    <a href="#js" class="nav-link">JavaScript</a>
  </nav>
</header>

<section class="hero">
  <h1 class="hero-title">Learn web development</h1>
  <p class="hero-text">Free lessons and simple examples, on any device.</p>
  <a href="#start" class="hero-button">Get started</a>
</section>

<img class="banner-img" src="banner.jpg" width="800" height="300" alt="A wide banner image">

<section class="features">
  <article class="feature">
    <h2 class="feature-title">HTML</h2>
    <p class="feature-text">The structure of every page.</p>
  </article>
  <!-- three more features: CSS, JavaScript, Projects -->
</section>

<section class="two-col">
  <div class="article">
    <h2 class="article-title">Why responsive?</h2>
    <p>More than half of web visitors use a phone...</p>
  </div>
  <aside class="sidebar">
    <h2 class="sidebar-title">Quick tips</h2>
    <p>Start small. Add layout for bigger screens.</p>
  </aside>
</section>

In the practice editor, the banner image is a small built-in picture, so you do not need an image file.

Selector Element What changes with screen size
.site-header the <header> stacked on phones, a row on bigger screens
.logo, .main-nav, .nav-link the logo, the <nav> and its three links the nav is a small flex row
.hero, .hero-title, .hero-text, .hero-button the intro section and its contents the title gets bigger
.banner-img the <img> always scales down to fit
.features the <section> of four cards 1 column, then 2, then 4
.feature, .feature-title, .feature-text each card (they stay the same)
.two-col the <section> holding the article and sidebar stacked, then side by side
.article, .sidebar the two children main column and narrow side column

Mobile-first CSS

There are two ways to organise responsive CSS:

  • Desktop-first: write the full desktop design, then use max-width queries to undo and simplify things on small screens.
  • Mobile-first: write the simple small-screen design as your base styles with no media query, then use min-width queries to add more layout as the screen gets bigger.

Mobile-first is the standard approach, for good reasons:

  • The base layout (one column, stacked) is the simplest, and elements already stack by default. You write less CSS.
  • You add features as there is more space, instead of overriding and cancelling earlier rules.
  • Phones download and process only the simple rules that they need.
  • It forces you to decide what really matters on a small screen.

Here is the same idea written both ways, for a card grid:

/* MOBILE-FIRST (recommended): base = small screens, then add */
.features {
  display: grid;
  grid-template-columns: 1fr;
}

@media (min-width: 600px) {
  .features { grid-template-columns: repeat(2, 1fr); }
}

@media (min-width: 900px) {
  .features { grid-template-columns: repeat(4, 1fr); }
}
/* DESKTOP-FIRST: base = big screens, then undo */
.features {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
}

@media (max-width: 899px) {
  .features { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 599px) {
  .features { grid-template-columns: 1fr; }
}

Both produce the same result, but the first reads naturally (“start with one column; at 600px use two; at 900px use four”) and the rules never fight each other.

How a media query is written

@media (min-width: 600px) {
  /* rules that apply ONLY when the screen is at least 600px wide */
  .site-header {
    flex-direction: row;
  }
}

The parts are:

  • @media starts the query.
  • (min-width: 600px) is the condition. It is in brackets.
  • The { ... } block holds normal CSS rules, which apply only while the condition is true.

You can put as many rules as you like inside. Media queries add no specificity of their own: a rule inside a query is as strong as the same rule outside, so the later one wins. That is why your min-width queries must come after the base rules they are overriding.

The most useful features

Condition True when…
(min-width: 600px) the viewport is 600px wide or wider
(max-width: 599px) the viewport is 599px wide or narrower
(orientation: landscape) the screen is wider than it is tall
(hover: hover) the main input device can hover (a mouse), not a touchscreen
(prefers-color-scheme: dark) the visitor chose a dark system theme
(prefers-reduced-motion: reduce) the visitor asked for less animation
print the page is being printed

Combining conditions

/* AND: both must be true (a tablet-size range) */
@media (min-width: 600px) and (max-width: 899px) { ... }

/* OR: use a comma (either can be true) */
@media (max-width: 400px), (orientation: portrait) { ... }

/* NOT */
@media not print { ... }

Modern browsers also understand a cleaner range syntax that reads like maths: @media (width >= 600px) and @media (600px <= width < 900px). Both styles work, and you will see both in real projects.

Respecting visitor preferences

Media queries are not only about size. These two are good manners:

/* dark mode */
@media (prefers-color-scheme: dark) {
  body {
    background-color: #0f172a;
    color: #e2e8f0;
  }
}

/* turn off animation for visitors who ask for it */
@media (prefers-reduced-motion: reduce) {
  * {
    animation: none !important;
    transition: none !important;
  }
}

You will see a better way to do dark mode with CSS variables later in the course.

Choosing breakpoints

A breakpoint is the screen width at which your design changes. A very common mistake is to chase specific devices (“iPhone 15 is 393px wide”). There are hundreds of device sizes, and new ones appear every year. Instead:

Resize your page slowly and add a breakpoint where the design starts to look bad. The content decides the breakpoint, not the device.

Having said that, a small set of common values is a good starting point:

Breakpoint Typical change
base (below 600px) phones: single column, stacked
min-width: 600px large phones and small tablets: two columns
min-width: 900px tablets and laptops: side-by-side layouts
min-width: 1200px big screens: limit the width, add more columns

Keep these tips in mind:

  • Use few breakpoints. Two or three is plenty if your layout is flexible (Flexbox wrap, Grid auto-fit). Many breakpoints mean a lot of CSS to maintain.
  • Use the same values everywhere so that the whole site changes together.
  • Some developers write breakpoints in em (such as min-width: 37.5em), which also respond to a visitor’s larger default text size. Pixels are fine to start with.

Flexible images

A big image on a small screen overflows the page and creates sideways scrolling. The standard fix is three lines:

/* the <img class="banner-img"> */
.banner-img {
  display: block;
  max-width: 100%;      /* never wider than its container */
  height: auto;         /* keep the proportions */
}
  • max-width: 100% shrinks the image when the container is narrower than the image, but never makes it bigger than its real size.
  • height: auto stops it from stretching out of proportion.
  • display: block removes the small gap that inline images leave underneath.

Also give every <img> its width and height attributes, as in the HTML above. The browser then reserves the right amount of space before the image loads, so the page does not jump around. With height: auto in CSS, the image still scales correctly.

If an image must fill a box of a fixed shape and you accept some cropping, use object-fit:

.avatar {
  width: 100%;
  height: 200px;
  object-fit: cover;    /* fill the box and crop the edges, no stretching */
}

For serious cases (different crops or smaller files for phones), HTML offers srcset and <picture>. Those are HTML features, not CSS, but they belong in the same toolbox.

A complete mobile-first page

Here is the CSS for the practice page, in the order you should write it: base styles first, then queries for larger screens.

*, *::before, *::after { box-sizing: border-box; }

body {
  margin: 0;
  font-family: Arial, Helvetica, sans-serif;
  line-height: 1.6;
}

/* ---------- BASE (small screens) ---------- */

.site-header {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 12px 16px;
  background-color: #1e293b;
}

.logo { color: white; font-weight: bold; text-decoration: none; }
.main-nav { display: flex; gap: 16px; }
.nav-link { color: #cbd5e1; text-decoration: none; }

.hero {
  padding: 40px 16px;
  text-align: center;
  background-color: #eef2ff;
}

.hero-title { margin: 0 0 8px; font-size: 1.75rem; }
.hero-text  { margin: 0 0 16px; }

.hero-button {
  display: inline-block;
  padding: 10px 20px;
  background-color: royalblue;
  color: white;
  text-decoration: none;
  border-radius: 6px;
}

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

.features {
  display: grid;
  grid-template-columns: 1fr;       /* one column on phones */
  gap: 16px;
  padding: 16px;
}

.feature {
  padding: 20px;
  border: 1px solid #dddddd;
  border-radius: 12px;
}

.feature-title { margin: 0 0 4px; }
.feature-text  { margin: 0; }

.two-col {
  display: grid;                    /* stacked on phones */
  gap: 24px;
  padding: 16px;
}

.sidebar {
  padding: 16px;
  background-color: #f1f5f9;
}

/* ---------- 600px and wider ---------- */

@media (min-width: 600px) {
  .site-header {
    flex-direction: row;
    justify-content: space-between;
  }

  .hero-title { font-size: 2.25rem; }

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

/* ---------- 900px and wider ---------- */

@media (min-width: 900px) {
  .hero { padding: 80px 16px; }
  .hero-title { font-size: 3rem; }

  .features {
    grid-template-columns: repeat(4, 1fr);
  }

  .two-col {
    grid-template-columns: 1fr 280px;   /* article + fixed sidebar */
  }
}

Follow the story from top to bottom. On a phone, only the base rules apply: a stacked header, a single column of features, and the sidebar below the article. At 600px, the header becomes a row and the features form two columns. At 900px, there are four feature columns, and the article and sidebar sit side by side. Not one rule is ever “undone”.

Testing responsive pages

  • Drag your browser window narrower and wider and watch what happens.
  • Use the DevTools device toolbar (Ctrl + Shift + M, Cmd + Shift + M on Mac) to try phone and tablet sizes, and rotate the screen.
  • Look for sideways scrolling. It is the clearest sign that something is too wide.
  • Check on a real phone when you can. Touch targets, text size and scrolling feel different from a mouse.
  • Zoom the page to 200%. A good responsive layout survives.

In the MyCodeNest editor, the preview is as wide as its panel, so resize the browser window or open the preview in its own tab to trigger the breakpoints.

Common mistakes

  • Forgetting the viewport meta tag. Phones then show a tiny desktop page.
  • Writing min-width queries before the base rules, so the base rules override them. Queries come after.
  • Mixing min-width and max-width styles in one stylesheet without a plan, so rules overlap and conflict.
  • Using device names or exact device sizes for breakpoints instead of letting the content decide.
  • Too many breakpoints. Make the layout flexible first.
  • Fixed widths (width: 800px) on containers or images, which cause sideways scrolling.
  • Forgetting max-width: 100% on images, or using width: 100vw, which includes the scrollbar.
  • Hiding important content on mobile with display: none. Reorganise it rather than remove it.
  • Tiny tap targets. Links and buttons should be about 44 by 44 pixels so fingers can hit them.
  • Testing only on a big monitor.
  • Relying on hover for anything essential. Touch devices have no hover.

Practice

  1. Click Practice in Editor. Resize the browser window and find the width at which the header changes from stacked to a row. Move that breakpoint to 700px.
  2. Add a .features grid with one column, then two at min-width: 600px and four at min-width: 900px.
  3. Make .hero-title bigger on large screens with a media query.
  4. Make .two-col stack on phones and sit side by side from 900px.
  5. Add max-width: 100%; height: auto; to the banner image and compare it with and without those rules in a narrow window.
  6. Rewrite your features grid in a desktop-first way with max-width queries. Which version is easier to read?
  7. Add a prefers-color-scheme: dark query that changes the page background and text colour, then test it by switching your system theme.
  8. Remove the viewport meta tag from a page on your computer and view it on a phone. What happens?

Recap

  • Responsive design = flexible layouts + flexible media + media queries.
  • Always add <meta name="viewport" content="width=device-width, initial-scale=1">.
  • Write mobile-first: simple base styles for small screens, then add layout with @media (min-width: ...). Put queries after the base rules.
  • A media query is @media (condition) { rules }. Conditions can test width, orientation, hover, colour scheme, reduced motion and more, and can be combined with and, commas and not.
  • Choose breakpoints where your content breaks, keep them few, and reuse them.
  • Make images flexible with max-width: 100%; height: auto;, and give them width and height attributes.
  • Test by resizing, with the device toolbar, and on real phones.
  • Next you will learn two modern tools that reduce the need for breakpoints: clamp() for fluid sizes, and container queries for components that respond to their own space.