What Are CSS Media Queries?
A media query lets you apply CSS only when certain conditions match the user’s device or browser environment. The most common condition is viewport width (responsive design), but you can also react to things like orientation, aspect ratio, hover support, and color scheme.
- Use media queries when the layout genuinely needs to change at certain sizes or conditions.
- Don’t use them to micro-tune everything. Prefer flexible layouts (flex/grid, fluid units) first.
CSS Media Query Syntax
Media queries usually look like this:
@media (condition) { /* rules */ }
You can combine conditions with and and use commas for “OR”.
@media (max-width: 700px) {
.card {
border-style: dashed;
}
}
@media (min-width: 701px) and (max-width: 980px) {
.card {
border-style: dotted;
}
}
@media (min-width: 981px) {
.card {
border-style: solid;
}
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
margin: 0 auto;
padding: 18px;
font-family: system-ui, sans-serif;
}
.note {
margin: 0 0 12px;
padding: 10px 12px;
border: 2px solid #111;
background: #f7f7f7;
border-radius: 12px;
}
.card {
padding: 16px;
border: 4px solid #111;
border-radius: 16px;
background: #fff;
}
.kbd {
font-family: ui-monospace, SFMono-Regular, Menlo;
padding: 2px 6px;
border: 1px solid #111;
border-radius: 8px;
background: #f3f3f3;
}
Resize your browser (or the preview frame) and click snippets to see different query ranges. Tip: media queries react to the viewport, not the width of this card.
Border style changes based on viewport width: dashed (≤ 700px), dotted (701–980px), solid (≥ 981px).
Syntax Cheat Sheet
@media (max-width: 700px)matches up to 700px wide.@media (min-width: 701px)matches from 701px and up.@media (min-width: 600px) and (orientation: landscape)matches both conditions.@media (hover: hover), (pointer: fine)is an “OR” list (comma means “either”).
CSS Media Query Mobile
“Mobile” usually means: start with the small-screen layout as your default CSS, then add enhancements as the screen gets bigger. This is called mobile-first.
Why it’s nice: your base CSS is simpler, and you use min-width to progressively enhance.
/* Mobile-first: base is stacked */
.layout {
display: grid;
gap: 12px;
}
@media (min-width: 760px) {
.layout {
grid-template-columns: 1.2fr 0.8fr;
align-items: start;
}
}
/* Desktop-first: base is 2 columns */
.layout {
display: grid;
grid-template-columns: 1.2fr 0.8fr;
gap: 12px;
align-items: start;
}
@media (max-width: 759px) {
.layout {
grid-template-columns: 1fr;
}
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
margin: 0 auto;
padding: 18px;
font-family: system-ui, sans-serif;
}
.header {
margin: 0 0 12px;
}
.panel {
padding: 14px;
border: 3px solid #111;
border-radius: 14px;
background: #fff;
}
.panel h4 {
margin: 0 0 8px;
font-size: 16px;
}
.panel p {
margin: 0;
line-height: 1.5;
}
.main {
background: #f7f7f7;
}
.side {
background: #fff;
}
Snippet 1 is mobile-first (base stack, add columns at 760px). Snippet 2 is desktop-first (base columns, stack under 760px). Resize the viewport to see the difference.
Main content
This area grows larger and leads the layout.
CSS Media Query Min and Max
min-width and max-width are the bread and butter:
- Mobile-first: mostly
min-widthrules that add features as space increases. - Desktop-first: mostly
max-widthrules that simplify as space shrinks.
A very common pattern is to define ranges: small, medium, large.
@media (max-width: 599px) {
.badge {
transform: rotate(-3deg);
}
}
@media (min-width: 600px) and (max-width: 899px) {
.badge {
transform: rotate(0deg);
}
}
@media (min-width: 900px) {
.badge {
transform: rotate(3deg);
}
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
margin: 0 auto;
padding: 18px;
font-family: system-ui, sans-serif;
}
.badge {
width: min(520px, 100%);
padding: 16px;
border: 3px solid #111;
border-radius: 16px;
background: #fff;
transform-origin: 20% 60%;
}
.badge strong {
display: inline-block;
padding: 2px 8px;
border: 2px solid #111;
border-radius: 999px;
background: #f4f4f4;
}
Viewport rangesResize the viewport: small rotates left, medium stays flat, large rotates right.
min-width vs max-width: Which One Should I Use?
If you’re a beginner, a very good default is: mobile-first + min-width. It’s easier to reason about because your base CSS is the simplest version.
CSS Media Query Breakpoints
A breakpoint is where your layout “breaks” (looks cramped or awkward) and needs a new layout. The trick is: breakpoints should come from your design, not from random device lists.
- Build a flexible layout.
- Resize the viewport.
- When it looks poorly, add a breakpoint to fix that exact issue.
.cards {
display: grid;
gap: 12px;
grid-template-columns: 1fr;
}
@media (min-width: 520px) {
.cards {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 900px) {
.cards {
grid-template-columns: repeat(3, 1fr);
}
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 1020px;
margin: 0 auto;
padding: 18px;
font-family: system-ui, sans-serif;
}
.card {
padding: 14px;
border: 3px solid #111;
border-radius: 16px;
background: #fff;
line-height: 1.4;
}
.card strong {
display: block;
margin-bottom: 6px;
}
.hint {
margin: 0 0 12px;
padding: 10px 12px;
border: 2px solid #111;
border-radius: 12px;
background: #f7f7f7;
}
This uses “content-driven” breakpoints: 1 column by default, 2 columns at 520px, 3 columns at 900px. Resize the viewport to see the grid step up.
Card 1Short content.Card 2Some more content to vary height a bit.Card 3Hi.Card 4Responsive grids are a media query classic.Card 5Small, medium, large.Card 6That’s the whole trick.
Common Breakpoint Myths
- Myth: “There’s a perfect list of breakpoints.” Reality: your layout decides.
- Myth: “Every device needs its own breakpoint.” Reality: aim for a few sensible layout steps.
CSS Media Query Max Width
max-width is super common, and also super easy to misuse. Two beginner-friendly tips:
-
If you write
@media (max-width: 768px), that means: 768px and smaller. (Not “tablet only”.) -
If you stack multiple
max-widthqueries, order matters. Put smaller limits later if rules conflict.
.title {
font-size: 34px;
}
@media (max-width: 900px) {
.title {
font-size: 28px;
}
}
@media (max-width: 600px) {
.title {
font-size: 22px;
}
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
margin: 0 auto;
padding: 18px;
font-family: system-ui, sans-serif;
}
.box {
padding: 16px;
border: 3px solid #111;
border-radius: 16px;
background: #fff;
}
.box p {
margin: 8px 0 0;
line-height: 1.5;
}
Max-width orderingAs the viewport shrinks: 34px → 28px (≤ 900px) → 22px (≤ 600px). Notice the 600px rule is last, so it wins when both match.
CSS Media Query Sizes
“Sizes” can mean viewport dimensions (width, height), but also device capabilities.
Here are a few useful ones:
(orientation: portrait)or(orientation: landscape)(hover: hover)and(pointer: fine)to detect mouse-like input(prefers-color-scheme: dark)for dark mode preference(min-resolution: 2dppx)for high-density screens
:host {
--mode: "default";
}
.stage::before {
content: "Mode: " var(--mode);
}
@media (prefers-color-scheme: dark) {
:host {
--mode: "prefers dark scheme";
}
.stage {
background: #111;
color: #fff;
}
}
@media (hover: hover) and (pointer: fine) {
:host {
--mode: "mouse-like input";
}
.stage {
outline: 3px solid #333;
outline-style: dashed;
}
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
margin: 0 auto;
padding: 18px;
font-family: system-ui, sans-serif;
}
.stage {
position: relative;
padding: 16px;
border: 3px solid #111;
border-radius: 16px;
background: #fff;
outline: 3px solid transparent;
outline-offset: 4px;
}
.stage::before {
display: inline-block;
margin-bottom: 10px;
padding: 4px 10px;
border: 2px solid currentColor;
border-radius: 999px;
font-family: ui-monospace, SFMono-Regular, Menlo;
font-size: 13px;
}
.stage p {
margin: 0;
line-height: 1.5;
opacity: 0.9;
}
This playground uses media queries beyond width: your OS color scheme and your input device capabilities can change styles.
Note that the Code Playgrounds are using shadow DOM, so the :host selector is used to target the custom
element itself. In your project, you would typically use :root or another selector depending on your
structure.
Why Feature Queries Are Awesome
You can make UI decisions based on capabilities instead of guessing “mobile vs desktop”. For example, a hover tooltip can behave differently on devices that don’t support hover.
CSS Media Query Variable
This one is a common “wait, why doesn’t this work?” moment:
you generally can’t use CSS custom properties (variables like --bp)
inside a media query condition like (max-width: var(--bp)).
However, you can use variables as the result of media queries, which is very useful. You switch a variable value at different breakpoints, and then use it everywhere.
:host {
--mq: "base";
--pad: 12px;
}
.panel::before {
content: "Active: " var(--mq);
}
.panel {
padding: var(--pad);
}
@media (min-width: 700px) {
:host {
--mq: "≥ 700px";
--pad: 18px;
}
}
@media (min-width: 980px) {
:host {
--mq: "≥ 980px";
--pad: 26px;
}
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
margin: 0 auto;
padding: 18px;
font-family: system-ui, sans-serif;
}
.panel {
border: 3px solid #111;
border-radius: 16px;
background: #fff;
}
.panel::before {
display: inline-block;
margin-bottom: 10px;
padding: 4px 10px;
border: 2px solid #111;
border-radius: 999px;
background: #f4f4f4;
font-family: ui-monospace, SFMono-Regular, Menlo;
font-size: 13px;
}
.panel p {
margin: 0;
line-height: 1.5;
}
Media queries change variables, and variables control styles. Resize the viewport to see the label and padding update.
Can I Reuse Breakpoint Values Somehow?
In pure CSS today, your safest option is simply to repeat breakpoint values (yes, it’s a little repetitive), or group related rules together to reduce duplication.
There are also build-tool approaches (like PostCSS custom media) that let you define named breakpoints,
but that requires a build step. If you’re writing plain CSS in the browser, stick to standard @media.
CSS Media Query Aspect Ratio
Aspect ratio media queries react to the shape of the viewport, not just the size. This is great for “wide but short” screens versus “tall but narrow” screens.
(min-aspect-ratio: 16/9)means “at least as wide as 16:9”.(max-aspect-ratio: 4/3)means “not too wide”.
:host {
--shape: "default";
}
.hero::before {
content: "Viewport shape: " var(--shape);
}
@media (min-aspect-ratio: 16/9) {
:host {
--shape: "wide (≥ 16:9)";
}
.hero {
grid-template-columns: 1.2fr 0.8fr;
}
}
@media (max-aspect-ratio: 4/3) {
:host {
--shape: "tall-ish (≤ 4:3)";
}
.hero {
grid-template-columns: 1fr;
}
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 1020px;
margin: 0 auto;
padding: 18px;
font-family: system-ui, sans-serif;
}
.hero {
display: grid;
gap: 12px;
padding: 16px;
border: 3px solid #111;
border-radius: 18px;
background: #fff;
align-items: start;
}
.hero::before {
grid-column: 1 / -1;
display: inline-block;
justify-self: start;
padding: 4px 10px;
border: 2px solid #111;
border-radius: 999px;
background: #f4f4f4;
font-family: ui-monospace, SFMono-Regular, Menlo;
font-size: 13px;
}
.block {
padding: 14px;
border: 2px solid #111;
border-radius: 14px;
background: #f7f7f7;
}
.block h4 {
margin: 0 0 8px;
font-size: 16px;
}
.block p {
margin: 0;
line-height: 1.5;
}
Main
On wide screens we split into two columns.
Side
On tall-ish screens we stack into one column.
CSS Media Query Not Working
If a media query “does nothing”, it’s usually one of these issues. Let’s go through them.
1) You Forgot the Viewport Meta Tag
On mobile, if your page is missing the viewport meta tag, the browser may pretend the page is wider than it really is, making your breakpoints behave strangely.
Put this in your HTML <head>:
<meta name="viewport" content="width=device-width, initial-scale=1">
2) Your Query Never Matches
-
You wrote
(min-width: 900px)but you’re testing at 800px. (It won’t match. CSS is being honest, not rude.) -
You wrote
(max-width: 600px)and expected it to apply on desktop at 1200px. It won’t.
3) Your Rules Are Being Overridden
Media query rules still follow the normal cascade: later rules and more specific selectors can override earlier ones.
/* Media query tries to turn it green... */
@media (max-width: 800px) {
.button {
background: #0a7;
}
}
/* ...but this comes later, so it wins */
.button {
background: #111;
}
/* Fix 1: put the base rule first, then the media query */
.button {
background: #111;
}
@media (max-width: 800px) {
.button {
background: #0a7;
}
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
margin: 0 auto;
padding: 18px;
font-family: system-ui, sans-serif;
}
.box {
padding: 16px;
border: 3px solid #111;
border-radius: 16px;
background: #fff;
display: grid;
gap: 10px;
}
.button {
display: inline-block;
width: fit-content;
padding: 10px 14px;
border: 2px solid #111;
border-radius: 12px;
color: #fff;
text-decoration: none;
font-weight: 600;
}
.tip {
margin: 0;
line-height: 1.5;
opacity: 0.9;
}
ButtonSnippet 1 demonstrates a common cascade bug: the later base rule overrides the media query. Snippet 2 fixes it by ordering rules correctly.
Learn more about the CSS cascade in the CSS Cascade Interactive Tutorial, and about selector specificity in the CSS Specificity Interactive Tutorial.
4) You Are Testing the Wrong Thing
-
Media queries react to the viewport. Changing the width of a div does not trigger
(max-width). If you need “react to container width”, look into container queries (a different feature). - Zoom level can affect what you think you’re seeing. If something feels “off”, test at 100% zoom.
5) A Typo Breaks the Rule
A missing parenthesis, a wrong unit, or a misspelled feature can make the query invalid. Keep it boring and double-check:
@media (max-width: 700px) { ... }(good)@media (maxwidth: 700px) { ... }(nope)@media (max-width: 700) { ... }(also nope, needs a unit)
Wrap-Up and Next Steps
You now have the core media query toolkit: syntax, mobile-first design, min/max patterns, breakpoints, max-width gotchas, capability-based queries, variables (as outputs), aspect ratio, and debugging.
