CSS Media Queries: How to Make Your Site Responsive

- What a CSS Media Query Actually Does
- The Syntax, Piece by Piece
- min-width vs max-width: The Decision That Shapes Your CSS
- Choosing Breakpoints
- Modern Range Syntax
- Media Features Beyond Width
- The One Line That Makes It All Work
- A Complete, Practical Example
- When Media Queries Aren't the Right Tool
- Quick Reference
What a CSS Media Query Actually Does
A media query is a CSS rule that applies styles only when the browser meets a condition you specify, most often the width of the viewport. You wrap normal CSS in an @media block, and the browser runs those declarations only when the condition is true. That single mechanism is what lets one stylesheet serve a 360px phone and a 1440px monitor without shipping two separate sites.
Here is the whole idea in four lines:
.card {
padding: 1rem;
}
@media (min-width: 768px) {
.card {
padding: 2rem;
}
}
On narrow screens the card has 1rem of padding. Once the viewport reaches 768px, the second rule wins and padding becomes 2rem. Nothing else changes. Media queries never replace your CSS; they layer conditional overrides on top of it.
The Syntax, Piece by Piece
Every media query has up to three parts: a media type, one or more media features in parentheses, and the styles inside the braces.
@media screen and (min-width: 600px) and (max-width: 1199px) {
/* styles here apply only in this range */
}
screenis the media type. The others you will actually use areprint(for print stylesheets) andall(the default). The oldhandheld,tv, andauraltypes are deprecated, so ignore them.(min-width: 600px)is a media feature. Features test the environment: width, height, orientation, resolution, color scheme, and more.andcombines conditions. All of them must be true for the block to apply.
If you omit the media type, the default is all, so @media (min-width: 600px) is exactly equivalent to @media all and (min-width: 600px). That means it also applies when printing, not just on screen. This bare-feature form is the common shorthand precisely because it targets every medium. Add an explicit type only when you specifically mean to target print, or to scope styles to screen and keep them out of print.
Combining Conditions: and, comma, not
| Operator | Meaning | Example |
|---|---|---|
and |
All conditions must match | (min-width: 768px) and (orientation: landscape) |
, (comma) |
Either condition matches (OR) | (max-width: 600px), (orientation: portrait) |
not |
Negate the whole query | not all and (min-width: 768px) |
The comma is an OR: the styles apply if any comma-separated query is true. Mixing them lets you express fairly precise rules without JavaScript.
min-width vs max-width: The Decision That Shapes Your CSS
This is the question most developers get stuck on, so answer it first and the rest of your responsive CSS falls into place.
min-widthmeans "this width or wider." Styles cascade upward. This is the mobile-first approach: you write base styles for small screens, then addmin-widthqueries that enhance the layout as the screen grows.max-widthmeans "this width or narrower." Styles cascade downward. This is the desktop-first approach: you design for large screens, then usemax-widthqueries to walk the layout back down for phones.
These are two independent approaches to the same layout. They use different class names (.grid-mf and .grid-df) so you can drop both into one stylesheet to compare them without the base rules colliding. In a real project you would pick one approach and use a single .grid class.
/* Mobile-first: base styles are the phone layout */
.grid-mf {
display: block;
}
@media (min-width: 768px) {
.grid-mf {
display: grid;
grid-template-columns: repeat(2, 1fr);
}
}
/* Desktop-first: base styles are the desktop layout */
.grid-df {
display: grid;
grid-template-columns: repeat(2, 1fr);
}
@media (max-width: 767px) {
.grid-df {
display: block;
}
}
Both produce the same result here, but the mental models are different. Mobile-first is the modern default because base styles stay simple, you ship less override code, and the smallest devices (often the slowest) get the leanest CSS. If you want the full argument for why, see our guide on mobile-first CSS.
Watch the off-by-one gap
A classic bug: pairing max-width: 768px with min-width: 768px. At exactly 768px both rules can match, causing overlap. Keep ranges from colliding by using max-width: 767.98px, or better, avoid max-width in a mobile-first codebase entirely so you never need paired boundaries.
Choosing Breakpoints
A breakpoint is the viewport width where your layout changes. The single biggest mistake is picking breakpoints to match specific phones. Devices change every year; your content does not.
Set breakpoints where the design breaks, not where a device sits. Widen your browser slowly and watch. The moment a line of text gets too long, a card gets awkwardly wide, or whitespace balloons, that width is a breakpoint. This gives you a layout that looks intentional on hardware that does not exist yet.
That said, a conventional starting set is useful as a reference. These roughly follow the values popular frameworks converged on:
| Breakpoint | Common min-width |
Typical target |
|---|---|---|
| Small | 640px |
Large phones, small tablets portrait |
| Medium | 768px |
Tablets |
| Large | 1024px |
Small laptops, tablets landscape |
| Extra large | 1280px |
Desktops |
| 2XL | 1536px |
Large monitors |
Use these as a skeleton, then add or drop breakpoints based on what your actual content needs. Two well-placed breakpoints often beat five arbitrary ones.
Use rem, not px, for query units
Breakpoints written in px ignore the user's browser font-size preference. Written in rem, they scale with it, which is better for accessibility. 768px becomes 48rem (768 ÷ 16). Note that em/rem in media queries are always relative to the browser default (usually 16px), not the element's font size. If the difference between px, em, and rem is fuzzy, our CSS units guide untangles it.
@media (min-width: 48rem) {
/* 768px at the default font size, but respects user zoom */
}
Modern Range Syntax
Older media queries lean on min- and max- prefixes. Modern browsers support cleaner comparison operators that read like math:
/* Old way */
@media (min-width: 768px) and (max-width: 1199px) { }
/* New range syntax */
@media (768px <= width <= 1199px) { }
/* Single bound */
@media (width >= 768px) { }
The range syntax also sidesteps the off-by-one boundary problem, since < and <= are explicit. It is supported in all current versions of Chrome, Edge, Firefox, and Safari (Safari gained it in 16.4, early 2023). If you must support older browsers, keep the min-width/max-width form or provide it as a fallback.
Media Features Beyond Width
Width gets the attention, but several other features are genuinely useful in production.
prefers-color-scheme
Respect the user's system dark-mode setting with zero JavaScript:
:root {
--bg: #ffffff;
--text: #1a1a1a;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #1a1a1a;
--text: #f5f5f5;
}
}
prefers-reduced-motion
Users who get motion sickness can ask the OS to reduce animation. Honor it:
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
orientation and others
| Feature | What it tests | Example value |
|---|---|---|
orientation |
Portrait vs landscape | landscape |
aspect-ratio |
Viewport width-to-height ratio | 16/9 |
resolution |
Pixel density | 2dppx (retina) |
hover |
Whether the device can hover | hover / none |
pointer |
Precision of the pointer | fine / coarse |
hover and pointer are quietly powerful: they let you distinguish a mouse from a touchscreen and, for example, only show hover effects on devices that can actually hover.
The One Line That Makes It All Work
None of this responds correctly on real phones without the viewport meta tag in your HTML <head>:
<meta name="viewport" content="width=device-width, initial-scale=1">
Without it, mobile browsers pretend to be about 980px wide and zoom out, so your min-width queries fire against the wrong number and the page looks like a shrunken desktop. This single tag is the most common reason "my media queries aren't working." Add it to every page.
A Complete, Practical Example
Here is a mobile-first card grid that adapts across three breakpoints, uses rem units, and respects dark mode:
.cards {
display: grid;
gap: 1rem;
grid-template-columns: 1fr;
padding: 1rem;
}
@media (min-width: 40rem) {
.cards {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 64rem) {
.cards {
grid-template-columns: repeat(3, 1fr);
gap: 1.5rem;
padding: 2rem;
}
}
One column on phones, two on tablets, three on desktops, all from a single stylesheet with no JavaScript. That is the entire promise of responsive design delivered by three small blocks.
When Media Queries Aren't the Right Tool
Media queries respond to the viewport, not to the space a component actually occupies. A sidebar card and a full-width card have the same viewport but very different available widths. For component-level responsiveness, container queries (@container) are the better tool, and they are now supported in all major browsers. Media queries remain the right choice for page-level layout, global spacing, dark mode, and motion preferences. Both fit into the bigger picture covered in our responsive design guide.
Quick Reference
- Wrap conditional styles in
@media (condition) { }. - Prefer
min-widthand write mobile-first. - Set breakpoints where content breaks, not where devices sit.
- Use
remunits so breakpoints respect user zoom. - Combine conditions with
and, OR them with commas, and reach for range syntax (width >= 48rem) in modern projects. - Always include the viewport meta tag.
- Use
prefers-color-schemeandprefers-reduced-motionto respect user preferences for free.
Master those seven habits and media queries stop being a source of mystery bugs and become the reliable backbone of every responsive site you build.