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.
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:
- Flexible layouts: widths in
%,fr,max-width, Flexbox and Grid instead of fixed pixel widths. You have already learned most of this. - Flexible media: images and videos that shrink to fit their container.
- 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-widthqueries 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-widthqueries 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:
@mediastarts 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 asmin-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: autostops it from stretching out of proportion.display: blockremoves 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 + Mon 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-widthqueries before the base rules, so the base rules override them. Queries come after. - Mixing
min-widthandmax-widthstyles 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 usingwidth: 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
- 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. - Add a
.featuresgrid with one column, then two atmin-width: 600pxand four atmin-width: 900px. - Make
.hero-titlebigger on large screens with a media query. - Make
.two-colstack on phones and sit side by side from900px. - Add
max-width: 100%; height: auto;to the banner image and compare it with and without those rules in a narrow window. - Rewrite your features grid in a desktop-first way with
max-widthqueries. Which version is easier to read? - Add a
prefers-color-scheme: darkquery that changes the page background and text colour, then test it by switching your system theme. - Remove the
viewportmeta 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 withand, commas andnot. - Choose breakpoints where your content breaks, keep them few, and reuse them.
- Make images flexible with
max-width: 100%; height: auto;, and give themwidthandheightattributes. - 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.