A media query applies CSS only when a condition about the device or the user's settings is true. @media (width >= 48rem) { ... } targets wider screens; prefers-color-scheme, prefers-reduced-motion, hover and pointer describe settings and input. Write the small-screen styles first, then add min-width queries for larger layouts.
| Goal | Query |
|---|---|
| Tablet and up | @media (width >= 48rem) |
| Phones only | @media (width < 40rem) |
| Dark mode | @media (prefers-color-scheme: dark) |
| Less animation | @media (prefers-reduced-motion: reduce) |
| Mouse users | @media (hover: hover) and (pointer: fine) |
| Printing | @media print |
Updated
The panel below runs sixteen queries through window.matchMedia on your own browser and updates as you resize the window, rotate a phone or switch dark mode. Under it: the range syntax, user preference queries, input queries, framework breakpoints, and when a container query is the better tool.
| Group | Query | Matches when |
|---|---|---|
| Width | (width < 40rem) | Narrower than Tailwind sm (640px at default font size) |
| Width | (40rem <= width < 64rem) | Between Tailwind sm and lg |
| Width | (width >= 64rem) | Tailwind lg and up |
| Width | (min-width: 768px) | Classic tablet breakpoint, old syntax |
| Orientation | (orientation: portrait) | Viewport taller than wide |
| Orientation | (orientation: landscape) | Viewport wider than tall |
| Input | (hover: hover) | Main input can hover (mouse, trackpad) |
| Input | (pointer: coarse) | Main input is imprecise (finger) |
| Input | (pointer: fine) | Main input is precise (mouse, stylus) |
| Input | (any-hover: hover) | At least one input can hover |
| Preference | (prefers-color-scheme: dark) | System set to dark mode |
| Preference | (prefers-reduced-motion: reduce) | User asked for less motion |
| Preference | (prefers-contrast: more) | User asked for more contrast |
| Display | (min-resolution: 2dppx) | High density screen, 2x or more |
| Display | (display-mode: standalone) | Running as an installed web app |
| Display | Printing or print preview |
Values from the official Tailwind CSS v4 and Bootstrap 5 documentation. Tailwind's are in rem, shown here at the default 16px.
| Name | Tailwind v4 | Bootstrap 5 |
|---|---|---|
| sm | 40rem (640px) | 576px |
| md | 48rem (768px) | 768px |
| lg | 64rem (1024px) | 992px |
| xl | 80rem (1280px) | 1200px |
| 2xl / xxl | 96rem (1536px) | 1400px |
Range syntax reads like maths and makes non-overlapping bands easy: < on one side, >= on the other.
/* classic */
@media (min-width: 768px) and (max-width: 1023.98px) { .nav { gap: 1rem; } }
/* range syntax */
@media (768px <= width < 1024px) { .nav { gap: 1rem; } }
/* mobile first: base, then larger */
.grid { display: grid; gap: 1rem; }
@media (width >= 48rem) { .grid { grid-template-columns: 1fr 1fr; } }
@media (width >= 80rem) { .grid { grid-template-columns: repeat(4, 1fr); } }Small additions that make a site feel native on every device.
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; }
}
@media (hover: hover) and (pointer: fine) {
.card:hover { transform: translateY(-2px); }
}
@media (pointer: coarse) {
.icon-button { min-width: 44px; min-height: 44px; }
}
@media print {
nav, .ads { display: none; }
}matchMedia returns an object with matches and a change event. This is what the live panel on this page uses.
const dark = window.matchMedia("(prefers-color-scheme: dark)");
console.log(dark.matches);
dark.addEventListener("change", (e) => {
document.documentElement.dataset.theme = e.matches ? "dark" : "light";
});| Media query | Container query | |
|---|---|---|
| Looks at | Viewport, device, user settings | Size or style of a parent container |
| Setup | None | container-type: inline-size on the parent |
| Syntax | @media (width >= 48rem) | @container (width >= 30rem) |
| Best for | Page layout, preferences, print | Reusable components placed in columns of any width |
| Tailwind v4 | md:flex | @container on the parent, @md:flex on the child |
Both cards have the same markup. Only the wide one has room to switch to a row layout. Edit the widths to see it change.
<style>
.wrap { container-type: inline-size; border: 1px dashed #94a3b8; margin-bottom: 8px }
.card { display: grid; gap: 8px; padding: 10px; font: 14px system-ui }
.card div { background: #bfdbfe; height: 40px; border-radius: 6px }
@container (width >= 320px) { .card { grid-template-columns: 100px 1fr } }
</style>
<div class="wrap" style="width: 220px"><div class="card"><div></div><p style="margin:0">Narrow container</p></div></div>
<div class="wrap" style="width: 400px; max-width: 100%"><div class="card"><div></div><p style="margin:0">Wide container</p></div></div>| Variant | CSS |
|---|---|
| md: | @media (width >= 48rem) |
| max-md: | @media (width < 48rem) |
| md:max-xl: | 48rem and up, below 80rem |
| min-[900px]: | @media (width >= 900px) |
| dark: | @media (prefers-color-scheme: dark) by default |
| motion-reduce: | @media (prefers-reduced-motion: reduce) |
| portrait: / landscape: | @media (orientation: portrait) / (orientation: landscape) |
| print: | @media print |
A query is an optional media type (screen, print or all) followed by feature tests in parentheses. @media screen and (orientation: landscape) needs both to be true. A comma between queries means either one is enough.
Queries do not add specificity. The rule inside wins or loses by normal cascade order, which is why responsive overrides usually go after the base styles in the file.
hover and pointer describe the primary input; any-hover and any-pointer describe all of them. A laptop with a touch screen typically reports hover: hover and pointer: fine for its trackpad, while any-pointer: coarse is also true because of the screen.
Preference queries reflect choices the person made in their system settings. Honouring prefers-reduced-motion is an accessibility requirement in practice, and prefers-color-scheme lets the site match the rest of their screen.
A shorter way to write width conditions with comparison operators: (width >= 600px) instead of (min-width: 600px), and (400px <= width < 900px) for a band. It is supported in all current major browsers; Safari added it in 16.4 in 2023, so check caniuse if you support older devices.
Mobile-first stylesheets use min-width: base styles serve small screens and each query adds layout for wider ones. That keeps small-screen CSS simple. max-width is fine for small exceptions, but mixing both at the same breakpoint can leave a one-pixel gap or overlap; range syntax with < and >= avoids that.
Use @media (prefers-color-scheme: dark) and redefine your colour variables inside it. In JavaScript, window.matchMedia('(prefers-color-scheme: dark)').matches gives the same answer, and its change event fires when the user switches.
Media queries look at the viewport and the device. Container queries look at the size of a parent element marked with container-type. A card that should change layout when its column is narrow, wherever it is placed, wants a container query.
Common causes: the viewport meta tag is missing, so phones pretend to be about 980px wide; the query sits above the rule it should override and loses on source order; or there is a missing space or parenthesis, which silently invalidates the whole condition.
Related: Screen resolution and viewport tool, CSS variables, Modern CSS features, Flexbox and grid cheat sheet, Scroll animations.
Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.
We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.