zudo-css-wisdom

Type to search...

to open search from anywhere

Media Query Best Practices

CreatedMar 13, 2026UpdatedApr 24, 2026Takeshi Takatsudo

Use media queries for page-level layout and user preferences with a mobile-first approach.

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.

Mobile-First Layout — Use viewport buttons to see breakpoints

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, or prefers-contrast queries.
  • Using media queries for component layouts: Using @media when @container would be more appropriate. Media queries are for page-level layout; container queries are for component-level adaptation.
  • Missing @media (hover: hover): Adding :hover styles that create sticky hover states on touch devices.
  • Not using @supports: Writing modern CSS features without fallbacks and without checking support.
  • Using px for breakpoints: Pixel breakpoints do not scale with user font-size preferences. Use rem values (e.g., 48rem instead of 768px).
  • 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, pointer for adjusting interactions to input type.
  • Feature detection: @supports for progressive enhancement with new CSS features.
  • Not for component layouts: Use container queries instead.
  • Not for fluid sizing: Use clamp() instead of breakpoint jumps.

References

Revision History