Media Query Best Practices
The Problem
AI agents use media queries as the default (and often only) responsive tool. They frequently use arbitrary device-based breakpoints, ignore user preference queries (prefers-reduced-motion, prefers-color-scheme, prefers-contrast), and never use feature queries (@supports). They also tend to write desktop-first styles and then override everything for mobile, leading to bloated CSS.
The Solution
Media queries should be one tool among many for responsive design. Use them for page-level layout changes and user preference detection. Prefer content-driven breakpoints over device-specific ones, and adopt a mobile-first approach.
Code Examples
Mobile-First vs. Desktop-First
Mobile-first uses min-width queries, starting from the smallest screen and adding complexity:
/* Mobile-first: base styles are for small screens */
.layout {
display: flex;
flex-direction: column;
gap: 1rem;
}
@media (min-width: 48rem) {
.layout {
flex-direction: row;
}
}
@media (min-width: 64rem) {
.layout {
max-width: 75rem;
margin-inline: auto;
}
}
Desktop-first uses max-width queries, starting from the largest screen and removing features:
/* Desktop-first: more overrides needed */
.layout {
display: flex;
flex-direction: row;
max-width: 75rem;
margin-inline: auto;
}
@media (max-width: 63.999rem) {
.layout {
max-width: none;
}
}
@media (max-width: 47.999rem) {
.layout {
flex-direction: column;
}
}
Mobile-first results in less CSS overall because you add styles as the viewport grows rather than removing them.
Content-Driven Breakpoints
Instead of targeting specific devices, add breakpoints where your layout breaks:
/* Let the content dictate the breakpoint */
.article {
max-width: 65ch; /* Optimal reading width */
margin-inline: auto;
padding-inline: 1rem;
}
.article-grid {
display: grid;
grid-template-columns: 1fr;
gap: 1.5rem;
}
/* Add a second column when there is enough room */
@media (min-width: 55rem) {
.article-grid {
grid-template-columns: 1fr 20rem;
}
}
User Preference: prefers-color-scheme
:root {
--color-text: #1a1a1a;
--color-bg: #ffffff;
--color-surface: #f5f5f5;
--color-border: #e0e0e0;
}
@media (prefers-color-scheme: dark) {
:root {
--color-text: #e0e0e0;
--color-bg: #1a1a1a;
--color-surface: #2a2a2a;
--color-border: #3a3a3a;
}
}
body {
color: var(--color-text);
background-color: var(--color-bg);
}
Allow manual override with a data attribute:
[data-theme="light"] {
--color-text: #1a1a1a;
--color-bg: #ffffff;
--color-surface: #f5f5f5;
--color-border: #e0e0e0;
}
[data-theme="dark"] {
--color-text: #e0e0e0;
--color-bg: #1a1a1a;
--color-surface: #2a2a2a;
--color-border: #3a3a3a;
}
User Preference: prefers-reduced-motion
/* Remove transitions and animations for users who prefer reduced motion */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
See the dedicated prefers-reduced-motion page for a more nuanced approach.
User Preference: prefers-contrast
@media (prefers-contrast: more) {
:root {
--color-text: #000000;
--color-bg: #ffffff;
--color-border: #000000;
}
.button {
border: 2px solid currentColor;
}
}
@media (prefers-contrast: less) {
:root {
--color-text: #333333;
--color-bg: #fafafa;
--color-border: #cccccc;
}
}
Interaction Media Queries: hover and pointer
/* Only apply hover styles on devices that support hover */
@media (hover: hover) {
.card {
transition: box-shadow 0.2s ease;
}
.card:hover {
box-shadow: 0 4px 12px rgb(0 0 0 / 0.15);
}
}
/* Increase touch targets on coarse pointer devices */
@media (pointer: coarse) {
.nav-link {
min-height: 44px;
padding-block: 0.75rem;
}
}
Feature Queries with @supports
/* Base layout */
.grid {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.grid > * {
flex: 1 1 300px;
}
/* Enhanced layout for browsers with grid subgrid support */
@supports (grid-template-columns: subgrid) {
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}
.grid > * {
display: grid;
grid-template-rows: subgrid;
grid-row: span 3;
}
}
Combining Queries
/* Dark mode + reduced motion */
@media (prefers-color-scheme: dark) and (prefers-reduced-motion: reduce) {
.notification {
background-color: var(--color-surface);
/* No entrance animation, just appear */
}
}
Common AI Mistakes
- Device-specific breakpoints: Using
@media (max-width: 768px)because "that's the iPad width." Breakpoints should be driven by content, not device catalogs. - Desktop-first approach: Writing full desktop styles first and then stripping them away for mobile, creating unnecessary overrides.
- Ignoring user preferences: Never including
prefers-color-scheme,prefers-reduced-motion, orprefers-contrastqueries. - Using media queries for component layouts: Using
@mediawhen@containerwould be more appropriate. Media queries are for page-level layout; container queries are for component-level adaptation. - Missing
@media (hover: hover): Adding:hoverstyles that create sticky hover states on touch devices. - Not using
@supports: Writing modern CSS features without fallbacks and without checking support. - Using
pxfor breakpoints: Pixel breakpoints do not scale with user font-size preferences. Useremvalues (e.g.,48reminstead of768px). - Too many breakpoints: Creating 5+ breakpoints when
clamp()or intrinsic sizing would handle the fluid range.
When to Use
- Page-level layout changes: Switching between single-column and multi-column page layouts.
- User preference detection:
prefers-color-scheme,prefers-reduced-motion,prefers-contrast. - Input modality adaptation:
hover,pointerfor adjusting interactions to input type. - Feature detection:
@supportsfor progressive enhancement with new CSS features. - Not for component layouts: Use container queries instead.
- Not for fluid sizing: Use
clamp()instead of breakpoint jumps.