DEV Community

reactuse.com
reactuse.com

Posted on • Originally published at reactuse.com

useMediaQuery: Complete Guide to Responsive Design in React

CSS media queries handle most responsive styling, but sometimes your React components need to know about the viewport, user preferences, or device capabilities at the JavaScript level. Maybe you need to conditionally render a mobile navigation, detect dark mode, or respect reduced motion preferences. The useMediaQuery hook from ReactUse gives you a reactive boolean that stays in sync with any CSS media query string.

What is useMediaQuery?

useMediaQuery wraps the browser's window.matchMedia API in a React hook. Pass it a media query string and it returns a boolean. It subscribes to the change event internally, so the value updates automatically when conditions change.

import { useMediaQuery } from "@reactuses/core";

function Example() {
  const isMobile = useMediaQuery("(max-width: 768px)");
  return <p>{isMobile ? "Mobile view" : "Desktop view"}</p>;
}
Enter fullscreen mode Exit fullscreen mode

The API is minimal:

useMediaQuery(query: string, defaultState?: boolean) => boolean
Enter fullscreen mode Exit fullscreen mode

Basic Usage: Mobile Detection

The most common use case is detecting screen width for conditional rendering:

import { useMediaQuery } from "@reactuses/core";

function Navigation() {
  const isMobile = useMediaQuery("(max-width: 767px)");

  if (isMobile) {
    return (
      <button aria-label="Open menu">
        <HamburgerIcon />
      </button>
    );
  }

  return (
    <nav>
      <a href="/">Home</a>
      <a href="/about">About</a>
      <a href="/contact">Contact</a>
    </nav>
  );
}
Enter fullscreen mode Exit fullscreen mode

The component only re-renders when the boolean flips, not on every resize pixel.

Reusable Breakpoints Pattern

Define your breakpoints once and reuse them across your app:

import { useMediaQuery } from "@reactuses/core";

function useBreakpoint() {
  const isMobile = useMediaQuery("(max-width: 639px)");
  const isTablet = useMediaQuery("(min-width: 640px) and (max-width: 1023px)");
  const isDesktop = useMediaQuery("(min-width: 1024px)");
  return { isMobile, isTablet, isDesktop };
}

function Dashboard() {
  const { isMobile, isTablet } = useBreakpoint();
  const columns = isMobile ? 1 : isTablet ? 2 : 4;

  return (
    <div style={{ display: "grid", gridTemplateColumns: `repeat(${columns}, 1fr)`, gap: 16 }}>
      <Card title="Revenue" />
      <Card title="Users" />
      <Card title="Orders" />
      <Card title="Growth" />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Detecting User Preferences

Media queries go far beyond screen size. You can detect system-level preferences:

Dark Mode Detection

import { useMediaQuery } from "@reactuses/core";

function ThemeProvider({ children }: { children: React.ReactNode }) {
  const prefersDark = useMediaQuery("(prefers-color-scheme: dark)");

  return (
    <div style={{
      background: prefersDark ? "#1a1a2e" : "#ffffff",
      color: prefersDark ? "#e0e0e0" : "#1a1a1a",
      minHeight: "100vh",
    }}>
      {children}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Reduced Motion

This is critical for accessibility. Users with vestibular disorders set this preference at the OS level:

import { useMediaQuery } from "@reactuses/core";

function AnimatedCard({ children }: { children: React.ReactNode }) {
  const prefersReducedMotion = useMediaQuery("(prefers-reduced-motion: reduce)");

  return (
    <div style={{
      transition: prefersReducedMotion ? "none" : "transform 0.3s ease",
    }}>
      {children}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Other Useful Queries

const prefersHighContrast = useMediaQuery("(prefers-contrast: high)");
const isPortrait = useMediaQuery("(orientation: portrait)");
const hasHover = useMediaQuery("(hover: hover)");
Enter fullscreen mode Exit fullscreen mode

SSR and Hydration Safety

When rendering on the server, window.matchMedia does not exist. Without a defaultState, the hook returns false on the server and the real value on the client, causing a React hydration mismatch.

Pass a defaultState to prevent this:

const isMobile = useMediaQuery("(max-width: 768px)", false);
Enter fullscreen mode Exit fullscreen mode

In development mode, the hook logs a console warning if you forget defaultState during SSR.

Combining with Other Hooks

useMediaQuery pairs naturally with other ReactUse hooks. Here is a theme switcher that respects system preference but lets the user override:

import { useMediaQuery, useLocalStorage } from "@reactuses/core";

function ThemeSwitcher() {
  const systemPrefersDark = useMediaQuery("(prefers-color-scheme: dark)");
  const [userTheme, setUserTheme] = useLocalStorage<"light" | "dark" | "system">("theme", "system");

  const isDark = userTheme === "system" ? systemPrefersDark : userTheme === "dark";

  return (
    <div>
      <p>Current theme: {isDark ? "dark" : "light"}</p>
      <select value={userTheme} onChange={(e) => setUserTheme(e.target.value as "light" | "dark" | "system")}>
        <option value="system">System</option>
        <option value="light">Light</option>
        <option value="dark">Dark</option>
      </select>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Common Mistakes to Avoid

  1. Using window.matchMedia directly in render -- you get a stale snapshot without subscribing to changes.
  2. Forgetting defaultState with SSR -- always provide one in Next.js, Remix, or Astro projects.
  3. Too many individual queries -- group related breakpoints into a custom hook like useBreakpoint.

Installation

npm i @reactuses/core
# or
pnpm add @reactuses/core
# or
yarn add @reactuses/core
Enter fullscreen mode Exit fullscreen mode

FAQ

How is useMediaQuery different from CSS media queries?

CSS media queries apply styles declaratively. useMediaQuery gives you a JavaScript boolean so you can conditionally render components, run logic, or pass dynamic props. Use CSS for styling, use the hook when you need JavaScript-level awareness.

Does useMediaQuery cause performance issues?

No. Each call creates a single matchMedia listener, which is very lightweight. The component only re-renders when the boolean value actually changes, not on every resize event.

Can I use useMediaQuery with Next.js or other SSR frameworks?

Yes. Pass a defaultState parameter to avoid hydration mismatches. For example: useMediaQuery("(max-width: 768px)", false). The hook will use the default on the server and update to the real value on the client.

What media queries can I use besides screen width?

You can use any valid CSS media query: prefers-color-scheme, prefers-reduced-motion, prefers-contrast, orientation, hover, pointer, and more.


ReactUse provides 100+ production-ready hooks for React. Explore them all at reactuse.com →

Top comments (0)