CSS Media Queries: 10 Code-Along Examples

Learn CSS media queries by building them. Ten copy-and-run examples covering min-width and max-width, mobile-first layouts, breakpoint ladders, combining conditions, orientation, high-resolution assets, dark mode, and the motion and pointer queries.

The CSS media queries article states the purpose plainly: media queries let one stylesheet apply different styles depending on the device or viewport, which is what makes a single page work on a phone and a desktop alike. This workbook runs the full range. It starts with the two width directions and the mobile-first approach the article recommends, moves through combining conditions, orientation, and high-resolution displays, and finishes with the modern preference queries, dark mode and reduced motion, that make a site adapt to the person, not just the screen. The habit to build early is mobile-first: write the small-screen layout as your base, then add complexity upward with min-width.

Shared setup

Every example uses this index.html. Only the styles.css changes:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Media queries</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<header class="banner">Responsive layout</header>
<main class="grid">
<div class="card">One</div>
<div class="card">Two</div>
<div class="card">Three</div>
</main>
</body>
</html>

The <meta name="viewport"> tag is not optional: without it, mobile browsers pretend to be desktop-width and every media query below misfires. It is the first line of any responsive page.

1. Your first media query

A media query is the @media rule wrapping a block of CSS that applies only when its condition is true. Here the background changes on narrow screens.

body { margin: 0; font-family: "Segoe UI", sans-serif; }
.banner { padding: 24px; background: #1e2331; color: #fff; font-size: 1.5rem; }
/* applies ONLY when the viewport is 600px wide or less */
@media (max-width: 600px) {
.banner {
background: #6f7580;
font-size: 1.1rem;
}
}

The rule reads as a condition: everything inside @media (max-width: 600px) applies only while the viewport is 600 pixels wide or narrower, and is ignored otherwise. Resize past 600px and the banner reverts to its default dark style, because the query stops matching. This is the entire mechanism, one condition guarding one block, and every technique below is a variation on the condition. The point where behaviour changes, 600px here, is called a breakpoint.

2. min-width versus max-width: the two directions

The two width features point opposite ways. max-width styles screens up to a size, reading naturally as “on small screens”. min-width styles screens from a size upward, reading as “on large screens”. Choosing one sets the whole direction of your CSS.

body { margin: 0; font-family: "Segoe UI", sans-serif; }
.banner { padding: 24px; background: #1e2331; color: #fff; text-align: center; }
/* max-width: applies at 768px and BELOW (small screens) */
@media (max-width: 768px) {
.banner::after { content: " (small screen)"; }
}
/* min-width: applies at 769px and ABOVE (large screens) */
@media (min-width: 769px) {
.banner::after { content: " (large screen)"; }
}

The two queries are mirror images: max-width: 768px matches everything narrower than or equal to 768px, and min-width: 769px matches everything wider, so together they cover every screen with no overlap. The label in the banner switches as you cross the boundary. The deeper choice is which one you build with, because max-width leads to a desktop-first stylesheet that scales down and min-width leads to a mobile-first one that scales up, and the next example makes the case for the second.

3. Mobile-first: base styles, then enhance upward

The recommended approach writes the simplest layout, the single column phones need, as the default with no query, then uses min-width to add complexity as the screen grows. The base works everywhere, and each breakpoint only adds.

body { margin: 0; font-family: "Segoe UI", sans-serif; }
.banner { padding: 20px; background: #1e2331; color: #fff; }
/* BASE: mobile. one column, stacked. no media query needed. */
.grid { display: flex; flex-direction: column; gap: 16px; padding: 16px; }
.card { background: #eef0f4; padding: 32px; text-align: center; }
/* enhance: from 768px up, become a row */
@media (min-width: 768px) {
.grid { flex-direction: row; }
}

The base rules carry the phone layout and ship to every device, so the smallest, most constrained screen is never an afterthought bolted on at the end. The single min-width: 768px query then upgrades the stack into a row once there is room, which is the essence of progressive enhancement: start with what always works and add capability where the viewport allows. Mobile-first stylesheets tend to be shorter and less bug-prone than desktop-first ones, because you are adding rules as screens grow rather than overriding and undoing them as screens shrink.

4. A breakpoint ladder

Real layouts change at several sizes. Stacking min-width queries builds a ladder: one column on phones, two on tablets, three on laptops, each step adding to the last.

body { margin: 0; font-family: "Segoe UI", sans-serif; }
.grid { display: grid; gap: 16px; padding: 16px; grid-template-columns: 1fr; }
.card { background: #1e2331; color: #fff; padding: 40px; text-align: center; }
/* tablets and up: two columns */
@media (min-width: 768px) {
.grid { grid-template-columns: repeat(2, 1fr); }
}
/* laptops and up: three columns */
@media (min-width: 1024px) {
.grid { grid-template-columns: repeat(3, 1fr); }
}

The grid starts as a single column, then each min-width query redefines grid-template-columns to add a track, so widening the window steps the layout from one column to two to three. Because the queries are mobile-first and cumulative, each one only overrides the single property that needs to change, and the browser applies whichever queries currently match, with later matching rules winning. The article’s suggested breakpoints, roughly 480, 768, 1024, and 1280px, are sensible starting values, but the honest rule is to add a breakpoint wherever your specific content starts to look cramped, not at fixed device widths.

5. Combining conditions with and, and comma

Two operators build compound conditions. and requires both parts, which makes range queries, and the comma means “or”, letting several conditions share one block.

body { margin: 0; font-family: "Segoe UI", sans-serif; }
.banner { padding: 24px; background: #1e2331; color: #fff; text-align: center; }
/* AND: a range, only between 600px and 1024px (tablet-ish) */
@media (min-width: 600px) and (max-width: 1024px) {
.banner { background: #6f7580; }
.banner::after { content: " (mid-size)"; }
}
/* COMMA = OR: match very narrow screens OR portrait orientation */
@media (max-width: 480px), (orientation: portrait) {
.card { border: 3px solid #1e2331; }
}

The and operator chains conditions that must all be true, so (min-width: 600px) and (max-width: 1024px) matches only the band between those widths, which is how you target tablets specifically without affecting phones or desktops. The comma works the opposite way as an OR, applying the block if any of its conditions holds, so the card border appears on screens under 480px or in portrait orientation, whichever comes first. Between them, and narrows and comma broadens, and most real queries use one or the other rather than both at once.

6. Orientation: portrait and landscape

The orientation feature detects whether the viewport is taller than wide (portrait) or wider than tall (landscape), which matters most for full-height sections like heroes on phones.

body { margin: 0; font-family: "Segoe UI", sans-serif; }
.banner {
display: flex; align-items: center; justify-content: center;
background: #1e2331; color: #fff; font-size: 1.5rem;
}
/* portrait: a tall hero */
@media (orientation: portrait) {
.banner { height: 60vh; }
}
/* landscape: a short hero, so content isn't pushed off a short screen */
@media (orientation: landscape) {
.banner { height: 40vh; }
}

orientation: portrait matches whenever the viewport’s height exceeds its width, and landscape matches the reverse, so rotating a phone flips which block applies. The practical reason to care is visible here: a hero that fills 60 percent of the height looks right in portrait but swallows a phone held sideways, where 40 percent leaves room for the content below. Orientation queries are especially useful for the fold, video players, and image galleries, anywhere the ratio of the screen changes what a sensible height is.

7. High-resolution displays

Retina and other high-density screens pack more physical pixels into each CSS pixel, so a normal image can look soft. A resolution query serves a sharper asset only to the screens that can show it.

body { margin: 0; font-family: "Segoe UI", sans-serif; }
.banner {
height: 200px;
background-image: url("logo.png"); /* standard asset */
background-repeat: no-repeat;
background-position: center;
background-size: 200px auto;
}
/* high-density screens: serve the 2x asset, same displayed size */
@media (min-resolution: 192dpi),
(-webkit-min-device-pixel-ratio: 2) {
.banner {
background-image: url("logo@2x.png"); /* twice the pixels */
}
}

A standard display is around 96dpi, so min-resolution: 192dpi matches screens with roughly double the density, the retina threshold, and swaps in logo@2x.png, an image with twice the pixels displayed at the same 200px size so it renders crisply. The -webkit-min-device-pixel-ratio: 2 line is the older equivalent, included with a comma so Safari and older browsers also match. Note that the displayed background-size stays 200px in both cases; only the source image’s pixel count changes, which is the whole trick, more pixels crammed into the same space.

8. Dark mode with prefers-color-scheme

Modern queries respond to user preferences, not just screen dimensions. prefers-color-scheme: dark detects whether the operating system is set to dark mode and lets the page follow suit automatically.

/* LIGHT is the default */
body { margin: 0; font-family: "Segoe UI", sans-serif; background: #f7f9fc; color: #1e2331; }
.banner { padding: 24px; background: #eef0f4; color: #1e2331; }
.card { background: #ffffff; padding: 32px; border: 1px solid #d6dae1; }
/* follow the system into dark mode */
@media (prefers-color-scheme: dark) {
body { background: #1e2331; color: #eceff4; }
.banner{ background: #12151f; color: #eceff4; }
.card { background: #262c3d; border-color: #3a4156; color: #eceff4; }
}

The query reads a setting the user chose at the OS level, so someone who runs their phone in dark mode sees the dark palette without touching your site, and someone in light mode sees the light one, all from one stylesheet. The pattern is to write the light theme as the default and let the dark block override the colours that need to change, which keeps the two themes in sync. This is preference-based responsiveness rather than size-based, and it has quietly become an expectation: a site that ignores dark mode now feels dated on a device that respects it.

9. Respecting motion and pointer capability

Two more capability queries make a site considerate. prefers-reduced-motion honours users who asked their system to minimise animation, and hover/pointer detect whether the device even has a precise pointer that can hover.

body { margin: 0; font-family: "Segoe UI", sans-serif; }
.card {
background: #1e2331; color: #fff; padding: 32px; margin: 16px;
transition: transform 0.3s ease;
}
/* only add hover lift on devices that actually hover (not touchscreens) */
@media (hover: hover) and (pointer: fine) {
.card:hover { transform: translateY(-8px); }
}
/* honour a user's request for less motion */
@media (prefers-reduced-motion: reduce) {
.card { transition: none; }
}

The hover: hover and pointer: fine query asks whether the device has a mouse or trackpad that can genuinely hover with precision, so the lift-on-hover effect is added only there and not on touchscreens, where hover states are awkward or trigger on tap. The prefers-reduced-motion: reduce block respects an accessibility setting some users rely on to avoid nausea or distraction, switching the transition off for them. Both are the same idea as dark mode: query the person and their hardware, not just the viewport, and the site adapts to who is using it rather than assuming everyone is the same.

10. Putting it together: a responsive, adaptive component

The finale layers the techniques: a mobile-first card grid that ladders up through breakpoints, adds hover only where supported, and follows the system into dark mode, one component that adapts to size, capability, and preference at once.

/* BASE: mobile-first, light theme, single column */
body { margin: 0; font-family: "Segoe UI", sans-serif; background: #f7f9fc; color: #1e2331; }
.banner { padding: 20px; background: #eef0f4; text-align: center; }
.grid { display: grid; grid-template-columns: 1fr; gap: 16px; padding: 16px; }
.card {
background: #fff; border: 1px solid #d6dae1; padding: 32px; text-align: center;
transition: transform 0.25s ease;
}
/* SIZE: two columns from tablet, three from laptop */
@media (min-width: 768px) { .grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .grid { grid-template-columns: repeat(3, 1fr); } }
/* CAPABILITY: hover lift only on real pointers */
@media (hover: hover) and (pointer: fine) {
.card:hover { transform: translateY(-6px); }
}
/* PREFERENCE: dark mode, and reduced motion */
@media (prefers-color-scheme: dark) {
body { background: #1e2331; color: #eceff4; }
.banner { background: #12151f; }
.card { background: #262c3d; border-color: #3a4156; }
}
@media (prefers-reduced-motion: reduce) {
.card { transition: none; }
}

Every query type earns its place in one component. The base is the phone layout, the two min-width queries ladder it up to two and three columns, the hover/pointer query adds a lift only where hovering is real, and the two preference queries follow dark mode and honour reduced motion. Read together they answer three separate questions, how big is the screen, what can the device do, and what has the user asked for, and none of them override or fight each other because each guards a different property. This is what a modern responsive stylesheet looks like: not one layout forced to shrink, but a base that grows and adapts along several axes at once.

Work through these and you have the whole article in practice: the @media rule and its condition, min-width versus max-widthand the mobile-first case, the breakpoint ladder, and and comma for compound conditions, orientation, high-resolution asset swapping, dark mode, and the motion and pointer queries that adapt to the user. The principle the article leaves you with is the durable one: build mobile-first from a base that always works, add breakpoints where your content needs them rather than at fixed device sizes, and remember that the best queries respond to the person as much as the pixels.

See you soon.

View Comments (3)

Leave a Reply

Subscribe to My Newsletter

Subscribe to my email newsletter to get the latest posts delivered right to your email. Pure inspiration, zero spam.

Discover more from Discuss Data Science, Machine Learning and Analytics

Subscribe now to keep reading and get access to the full archive.

Continue reading