CSS Media Queries
CSS media queries are the cornerstone of responsive web design. They allow you to apply styles conditionally based on the characteristics of the device or viewport — most commonly its width, height, orientation, or user preferences. Media queries enable a single codebase to adapt seamlessly across phones, tablets, laptops, and large screens.
The syntax combines a media type (screen, print, all) with optional media features in parentheses, creating boolean conditions that match when all criteria are satisfied.
| 1 | @media screen and (max-width: 768px) { |
| 2 | .container { |
| 3 | flex-direction: column; |
| 4 | padding: 16px; |
| 5 | } |
| 6 | } |
| 7 | |
| 8 | /* Multiple conditions */ |
| 9 | @media (min-width: 768px) and (max-width: 1024px) { |
| 10 | .sidebar { |
| 11 | width: 200px; |
| 12 | } |
| 13 | } |
The @media at-rule is the core of responsive CSS. It can be used at the top level of a stylesheet or nested inside other conditional rules. It evaluates a media query list and applies the enclosed styles if the query matches.
| 1 | .box { |
| 2 | width: 100%; |
| 3 | max-width: 1200px; |
| 4 | margin: 0 auto; |
| 5 | } |
| 6 | |
| 7 | /* Apply styles when viewport is 768px or narrower */ |
| 8 | @media (max-width: 768px) { |
| 9 | .box { |
| 10 | padding: 12px; |
| 11 | } |
| 12 | } |
| 13 | |
| 14 | /* Media queries can also be used in the HTML link tag */ |
| 15 | /* |
| 16 | <link rel="stylesheet" href="mobile.css" media="screen and (max-width: 768px)"> |
| 17 | <link rel="stylesheet" href="print.css" media="print"> |
| 18 | */ |
Media types specify the general category of device a stylesheet applies to. The most commonly used types are screen, print, and all.
| 1 | .header { |
| 2 | background: linear-gradient(135deg, #0D0D0D, #1A1A2E); |
| 3 | } |
| 4 | |
| 5 | /* Print styles — hide non-essential elements */ |
| 6 | @media print { |
| 7 | .header { |
| 8 | background: none; |
| 9 | color: black; |
| 10 | } |
| 11 | .nav, .sidebar, .ad-banner { |
| 12 | display: none; |
| 13 | } |
| 14 | body { |
| 15 | font-size: 12pt; |
| 16 | } |
| 17 | } |
| 18 | |
| 19 | /* Media types */ |
| 20 | @media screen { |
| 21 | /* For all screen devices */ |
| 22 | } |
| 23 | |
| 24 | @media print { |
| 25 | /* For printed pages and print preview */ |
| 26 | } |
| 27 | |
| 28 | @media all { |
| 29 | /* For all devices (default) */ |
| 30 | } |
| 31 | |
| 32 | /* Speech (screen readers) */ |
| 33 | @media speech { |
| 34 | /* For aural/speech synthesizers */ |
| 35 | } |
Media features describe specific characteristics of the user's device or browsing environment. They are the conditional part of the media query that determines whether the styles apply.
Width & Height
| 1 | .layout { |
| 2 | display: grid; |
| 3 | grid-template-columns: 1fr 1fr 1fr; |
| 4 | gap: 24px; |
| 5 | } |
| 6 | |
| 7 | /* max-width — target sizes BELOW the threshold */ |
| 8 | @media (max-width: 1024px) { |
| 9 | .layout { grid-template-columns: 1fr 1fr; } |
| 10 | } |
| 11 | |
| 12 | /* min-width — target sizes ABOVE the threshold */ |
| 13 | @media (min-width: 1200px) { |
| 14 | .layout { grid-template-columns: 1fr 1fr 1fr 1fr; } |
| 15 | } |
| 16 | |
| 17 | /* Combining width and height */ |
| 18 | @media (min-width: 768px) and (max-width: 1024px) { |
| 19 | .layout { gap: 16px; } |
| 20 | } |
| 21 | |
| 22 | /* width and height are the most common features */ |
| 23 | /* |
| 24 | width — viewport width (min-/max- prefixes) |
| 25 | height — viewport height |
| 26 | device-width — deprecated, use width instead |
| 27 | aspect-ratio — width/height ratio |
| 28 | */ |
orientation
| 1 | .panel { |
| 2 | display: flex; |
| 3 | } |
| 4 | |
| 5 | @media (orientation: portrait) { |
| 6 | .panel { |
| 7 | flex-direction: column; |
| 8 | } |
| 9 | } |
| 10 | |
| 11 | @media (orientation: landscape) { |
| 12 | .panel { |
| 13 | flex-direction: row; |
| 14 | } |
| 15 | } |
| 16 | |
| 17 | /* Portrait: height > width */ |
| 18 | /* Landscape: width > height */ |
prefers-color-scheme
Respects the user's system preference for light or dark color scheme. This is essential for creating themes that match the OS setting.
| 1 | :root { |
| 2 | --bg: #FFFFFF; |
| 3 | --text: #0D0D0D; |
| 4 | --border: #E0E0E0; |
| 5 | } |
| 6 | |
| 7 | @media (prefers-color-scheme: dark) { |
| 8 | :root { |
| 9 | --bg: #0D0D0D; |
| 10 | --text: #E0E0E0; |
| 11 | --border: #222222; |
| 12 | } |
| 13 | } |
| 14 | |
| 15 | /* Terminal always uses dark, but respects color palette */ |
| 16 | .terminal { |
| 17 | background: #0D0D0D; |
| 18 | color: #00FF41; |
| 19 | } |
| 20 | |
| 21 | @media (prefers-color-scheme: light) { |
| 22 | .terminal { |
| 23 | border-color: #00FF41; |
| 24 | box-shadow: 0 0 20px rgba(0, 255, 65, 0.1); |
| 25 | } |
| 26 | } |
prefers-reduced-motion
Respects the user's system preference for reduced motion. This is critical for accessibility — many users experience motion sickness or vestibular disorders triggered by animations.
| 1 | .animated-element { |
| 2 | animation: slideIn 0.5s ease-out; |
| 3 | } |
| 4 | |
| 5 | @media (prefers-reduced-motion: reduce) { |
| 6 | .animated-element { |
| 7 | animation: none; |
| 8 | opacity: 1; |
| 9 | transform: none; |
| 10 | } |
| 11 | } |
| 12 | |
| 13 | /* Better approach: only animate when user prefers motion */ |
| 14 | @media (prefers-reduced-motion: no-preference) { |
| 15 | .fade-in { |
| 16 | animation: fadeIn 0.5s ease both; |
| 17 | } |
| 18 | } |
Other Useful Media Features
| 1 | .element { |
| 2 | --spacing: 24px; |
| 3 | } |
| 4 | |
| 5 | /* prefers-contrast — user wants more or less contrast */ |
| 6 | @media (prefers-contrast: high) { |
| 7 | .element { |
| 8 | border: 2px solid currentColor; |
| 9 | } |
| 10 | } |
| 11 | |
| 12 | /* prefers-transparency */ |
| 13 | @media (prefers-reduced-transparency: reduce) { |
| 14 | .glass { |
| 15 | background: rgba(13, 13, 13, 0.95); |
| 16 | backdrop-filter: none; |
| 17 | } |
| 18 | } |
| 19 | |
| 20 | /* prefers-color-scheme */ |
| 21 | @media (prefers-color-scheme: light) { |
| 22 | /* Overrides for light mode */ |
| 23 | } |
| 24 | |
| 25 | /* dynamic-range — HDR display detection */ |
| 26 | @media (dynamic-range: high) { |
| 27 | .hdr-content { |
| 28 | color: color(display-p3 0 1 0.5); |
| 29 | } |
| 30 | } |
| 31 | |
| 32 | /* pointer — coarse/fine pointing device */ |
| 33 | @media (pointer: coarse) { |
| 34 | /* Touch devices — larger tap targets */ |
| 35 | .button { min-height: 44px; } |
| 36 | } |
| 37 | |
| 38 | @media (pointer: fine) { |
| 39 | /* Mouse/stylus — tighter targets ok */ |
| 40 | .button { min-height: 32px; } |
| 41 | } |
| 42 | |
| 43 | /* hover — can the device hover? */ |
| 44 | @media (hover: hover) { |
| 45 | .card:hover { transform: translateY(-4px); } |
| 46 | } |
| 47 | |
| 48 | @media (hover: none) { |
| 49 | /* Touch devices — use active state instead */ |
| 50 | .card:active { transform: scale(0.98); } |
| 51 | } |
Media queries support logical operators for combining conditions: and (all conditions), not (negate), only (prevent older browsers), and comma-separated lists to represent OR logic.
| 1 | .panel { |
| 2 | display: flex; |
| 3 | gap: 24px; |
| 4 | } |
| 5 | |
| 6 | /* AND — all conditions must match */ |
| 7 | @media (min-width: 768px) and (max-width: 1024px) { |
| 8 | .panel { gap: 16px; } |
| 9 | } |
| 10 | |
| 11 | /* OR — comma-separated list */ |
| 12 | @media (max-width: 480px), (orientation: portrait) { |
| 13 | .panel { flex-direction: column; } |
| 14 | } |
| 15 | |
| 16 | /* NOT — negate a condition */ |
| 17 | @media not (hover: none) { |
| 18 | .card:hover { transform: translateY(-4px); } |
| 19 | } |
| 20 | |
| 21 | /* ONLY — prevent older browsers from applying the styles */ |
| 22 | @media only screen and (min-width: 768px) { |
| 23 | /* Only applies in browsers that understand media queries */ |
| 24 | } |
| 25 | |
| 26 | /* Equivalent queries */ |
| 27 | /* max-width: 768px is the same as width <= 768px */ |
| 28 | /* min-width: 768px is the same as width >= 768px */ |
Two primary philosophies guide how you structure media queries: mobile-first (min-width) and desktop-first (max-width). The choice affects how styles cascade and which breakpoints you target.
Mobile-First (min-width)
Start with styles for the smallest viewports, then add layers for larger screens using min-width. This is the recommended approach because it defaults to a simple stacked layout that works everywhere, and progressively enhances for larger screens.
| 1 | .layout { |
| 2 | /* Base: single column (mobile) */ |
| 3 | display: flex; |
| 4 | flex-direction: column; |
| 5 | gap: 16px; |
| 6 | } |
| 7 | |
| 8 | /* Tablet: 2 columns */ |
| 9 | @media (min-width: 640px) { |
| 10 | .layout { |
| 11 | flex-direction: row; |
| 12 | flex-wrap: wrap; |
| 13 | } |
| 14 | .layout > * { |
| 15 | flex: 1 1 calc(50% - 8px); |
| 16 | } |
| 17 | } |
| 18 | |
| 19 | /* Desktop: 3 columns */ |
| 20 | @media (min-width: 1024px) { |
| 21 | .layout > * { |
| 22 | flex: 1 1 calc(33.333% - 11px); |
| 23 | } |
| 24 | } |
| 25 | |
| 26 | /* Wide: 4 columns */ |
| 27 | @media (min-width: 1400px) { |
| 28 | .layout > * { |
| 29 | flex: 1 1 calc(25% - 12px); |
| 30 | } |
| 31 | } |
Desktop-First (max-width)
Start with desktop styles, then override for smaller viewports using max-width. This can be useful when retrofitting responsive design into an existing desktop-optimized site.
| 1 | .layout { |
| 2 | /* Base: 3 columns (desktop) */ |
| 3 | display: grid; |
| 4 | grid-template-columns: repeat(3, 1fr); |
| 5 | gap: 24px; |
| 6 | } |
| 7 | |
| 8 | /* Tablet */ |
| 9 | @media (max-width: 1024px) { |
| 10 | .layout { |
| 11 | grid-template-columns: repeat(2, 1fr); |
| 12 | gap: 16px; |
| 13 | } |
| 14 | } |
| 15 | |
| 16 | /* Mobile */ |
| 17 | @media (max-width: 640px) { |
| 18 | .layout { |
| 19 | grid-template-columns: 1fr; |
| 20 | gap: 12px; |
| 21 | } |
| 22 | } |
best practice
While breakpoints should be determined by your content — not specific devices — there are commonly referenced viewport widths that correspond to broad device categories.
| Breakpoint | Target | Typical Devices |
|---|---|---|
| 320px-480px | Mobile phones | iPhone SE, small Android phones |
| 481px-768px | Large phones / small tablets | iPhone Pro Max, iPad Mini portrait |
| 769px-1024px | Tablets | iPad portrait, small laptops |
| 1025px-1366px | Small laptops | 13" MacBook, Chromebooks |
| 1367px-1920px | Desktop / large monitors | 27" monitors, iMac |
| 1921px+ | Ultra-wide / 4K | Cinema displays, multi-monitor setups |
| 1 | .content { |
| 2 | max-width: 1200px; |
| 3 | margin: 0 auto; |
| 4 | padding: 24px; |
| 5 | } |
| 6 | |
| 7 | /* Mobile-first breakpoints using custom properties */ |
| 8 | :root { |
| 9 | --bp-sm: 640px; |
| 10 | --bp-md: 768px; |
| 11 | --bp-lg: 1024px; |
| 12 | --bp-xl: 1280px; |
| 13 | --bp-2xl: 1536px; |
| 14 | } |
| 15 | |
| 16 | /* SM: 640px+ */ |
| 17 | @media (min-width: 640px) { /* ... */ } |
| 18 | /* MD: 768px+ */ |
| 19 | @media (min-width: 768px) { /* ... */ } |
| 20 | /* LG: 1024px+ */ |
| 21 | @media (min-width: 1024px) { /* ... */ } |
| 22 | /* XL: 1280px+ */ |
| 23 | @media (min-width: 1280px) { /* ... */ } |
| 24 | /* 2XL: 1536px+ */ |
| 25 | @media (min-width: 1536px) { /* ... */ } |
info
Container queries are a more recent addition to CSS that allow styling based on the size of a parent container rather than the viewport. While media queries respond to the global viewport size, container queries enable truly reusable components that adapt to their placement context.
| 1 | .card-container { |
| 2 | container-type: inline-size; |
| 3 | container-name: card; |
| 4 | } |
| 5 | |
| 6 | /* Media query — responds to viewport */ |
| 7 | @media (min-width: 768px) { |
| 8 | .card { flex-direction: row; } |
| 9 | } |
| 10 | |
| 11 | /* Container query — responds to parent container */ |
| 12 | @container card (min-width: 400px) { |
| 13 | .card { |
| 14 | flex-direction: row; |
| 15 | } |
| 16 | } |
| 17 | |
| 18 | /* Comparison: same component, different contexts */ |
| 19 | /* |
| 20 | Media query: .card is row when viewport > 768px |
| 21 | Container: .card is row when its parent > 400px |
| 22 | The container query makes .card truly reusable — |
| 23 | it adapts to wherever it is placed, not just viewport |
| 24 | */ |
| Aspect | Media Queries | Container Queries |
|---|---|---|
| Scope | Viewport / device | Parent container |
| Reusability | Limited — component locked to viewport | Full — component adapts to any context |
| Browser support | Universal | Modern browsers (2023+) |
| Use case | Page-level layout | Component-level layout |
note