CSS Tutorial
CSS Media Queries
@media tests viewport, pointer, or preference and swaps rules. Mobile-first min-width is the usual pattern.
Rules that only apply sometimes
A stylesheet does not have to be one set of declarations for every screen. @media wraps extra rules that apply when a test is true: the viewport is at least 40rem wide, the pointer is coarse, the user asked for less motion. Outside the block, the base styles still run.
Harbor studio’s desk grid is one column on a phone and three on a desk. The HTML does not change. The media query swaps grid-template-columns. That is the whole idea.
Mobile-first min-width
Write the narrow layout first, with no query. Then add @media (min-width: …) blocks that enhance as the viewport grows. You never fight a desktop layout back down to a phone. Each query only adds what extra space allows.
max-width queries do the opposite: desktop first, then carve exceptions for small screens. That works, but you end up undoing columns, hover-only menus, and wide padding. Beginners should start with min-width.
| Feature | Example test | When it is true |
|---|---|---|
| Viewport width | (min-width: 40rem) | Layout viewport is at least 40rem |
| Viewport width | (max-width: 39.99rem) | Narrower than 40rem — desktop-first |
| Pointer | (hover: hover) | A fine pointer that can hover |
| Motion | (prefers-reduced-motion: reduce) | The user asked to cut animation |
| Scheme | (prefers-color-scheme: dark) | OS dark theme |
Example
<style>
body {
font-family: system-ui, sans-serif;
color: #0c4a6e;
background: #f0f9ff;
margin: 1.25rem;
}
.desks {
display: grid;
grid-template-columns: 1fr;
gap: 0.65rem;
}
.desk {
padding: 0.8rem 1rem;
background: #fff;
border: 1px solid #7dd3fc;
border-radius: 0.5rem;
}
h2 { margin: 0 0 0.25rem; font-size: 1rem; color: #0d9488; }
p { margin: 0; color: #475569; font-size: 0.9rem; }
@media (min-width: 36rem) {
.desks { grid-template-columns: 1fr 1fr 1fr; }
}
</style>
<h1>Harbor studio</h1>
<p>One column by default. Three columns when the preview is at least 36rem wide.</p>
<div class="desks">
<article class="desk"><h2>Tide</h2><p>Window</p></article>
<article class="desk"><h2>Pier</h2><p>Quiet</p></article>
<article class="desk"><h2>Loft</h2><p>Pair</p></article>
</div>Prefer rem in the query so the breakpoint scales with root type. A 600px query stays 600 pixels when the visitor enlarges text; a 37.5rem query moves with them. Resize the pane at/css/try to cross 36rem.
Layering breakpoints
You can stack several min-width blocks. Each one should only add or replace what that width needs. Do not copy the entire stylesheet into every query. Override the two or three properties that change.
Example
<style>
body {
font-family: system-ui, sans-serif;
color: #0c4a6e;
background: #f0f9ff;
margin: 1.25rem;
}
.page {
display: grid;
gap: 0.75rem;
}
header {
padding: 0.75rem 1rem;
background: #0ea5e9;
color: #fff;
border-radius: 0.45rem;
}
main, aside {
padding: 0.85rem 1rem;
border-radius: 0.45rem;
}
main { background: #fff; border: 1px solid #bae6fd; }
aside { background: #ccfbf1; }
h1, h2 { margin: 0 0 0.3rem; font-size: 1.05rem; }
p { margin: 0; }
@media (min-width: 28rem) {
header { padding: 1rem 1.25rem; }
}
@media (min-width: 44rem) {
.page { grid-template-columns: 1fr 11rem; }
header { grid-column: 1 / -1; }
}
</style>
<div class="page">
<header><h1>Harbor studio</h1></header>
<main><h2>Print shop</h2><p>Stacked on small screens. Sidebar from 44rem.</p></main>
<aside><p>Open 8–16</p></aside>
</div>Below 44rem the page is a single column: header, main, aside in source order. From 44rem the template becomes two columns and the header spans both with grid-column: 1 / -1.
Not only width
Width is the query you will write most. Pointer and preference queries stop you from designing only for a mouse on a large, still screen. Hover styles that reveal essential links belong behind(hover: hover), with an equivalent that is always visible on touch. Motion that is decoration should yield to prefers-reduced-motion.
Example
<style>
body {
font-family: system-ui, sans-serif;
color: #0c4a6e;
background: #f0f9ff;
margin: 1.25rem;
}
.book {
display: inline-block;
padding: 0.65rem 1.1rem;
background: #0d9488;
color: #fff;
border: 0;
border-radius: 0.45rem;
font: inherit;
cursor: pointer;
transition: transform 180ms ease;
}
@media (hover: hover) {
.book:hover { transform: translateY(-2px); }
}
@media (prefers-reduced-motion: reduce) {
.book { transition: none; }
}
</style>
<h1>Harbor studio</h1>
<button class="book" type="button">Book a desk</button>Combine tests with and: @media (min-width: 40rem) and (hover: hover). A comma is an or. You rarely need a long chain. One clear min-width and one preference query cover most pages.
Worked examples
The short listings above show one property. These sheets paint documents you would actually ship: a lab dashboard, a clinic form, a marks table, a nav bar.
CSS does not invent meaning. HTML already said what the pieces are. Cascade, specificity, and the box model decide how they look. The numbers are classroom values — current, pH, pulse, 3-4-5 — so the style has something real to sit on.
Preview them in the CSS editor at /css/try. Change one property and watch the page paint. The tags stay the same.
Engineering
Mobile first, then two columns
@media (min-width: 400px) adds rules when the viewport is at least that wide. Start with a stack; enhance to a grid. That is mobile-first.
prefers-color-scheme and prefers-reduced-motion are queries too. A logger that ignores reduced motion is a migraine.
Example
<style>
.stack { display: grid; gap: 0.5rem; }
.stack div { background: #ccfbf1; padding: 0.5rem; }
@media (min-width: 400px) {
.stack { grid-template-columns: 1fr 1fr; }
}
</style>
<div class="stack"><div>Menu</div><div>Hours</div></div>Astronomy
A sky that follows the theme
Dark scheme is a preference, not a guess. Test both. Do not ship white text that only works in dark mode.
Example
<style>
p { color: #0f766e; }
@media (prefers-color-scheme: dark) {
p { color: #5eead4; }
}
</style>
<p>Mars notes follow the theme.</p>