zudo-css

Type to search...

to open search from anywhere

Media Query Best Practices

CreatedMar 13, 2026UpdatedMar 26, 2026Takeshi Takatsudo

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