A useMediaQuery Hook returns whether a CSS media query currently matches, and re-renders the component when that answer flips. It turns viewport breakpoints into ordinary React state that any component can read. It works for width, orientation, or any feature that CSS can test.
Read the query with matchMedia
window.matchMedia takes a query string and returns a MediaQueryList. Its matches property holds the current answer, and its change event fires only when the query starts or stops matching. This is more precise than listening for every resize event.
import { useEffect, useState } from "react";
function useMediaQuery(query) {
const [matches, setMatches] = useState(() => window.matchMedia(query).matches);
useEffect(() => {
const list = window.matchMedia(query);
const onChange = (event) => setMatches(event.matches);
list.addEventListener("change", onChange);
return () => list.removeEventListener("change", onChange);
}, [query]);
return matches;
}The lazy initializer reads the answer once during the first render, so the Hook never starts with a wrong value. The Effect subscribes to change and cleans up the listener when query changes or the component unmounts.
Strict Mode may run the Effect twice in development, and the cleanup makes that harmless. The change event carries the new matches value directly, so the listener never has to recompute it.
Use it for a responsive layout
Call the Hook with a query and branch on the returned boolean.
function Navigation() {
const isNarrow = useMediaQuery("(width <= 600px)");
return (
<nav>
{isNarrow ? <button>Menu</button> : <span>Full navigation links</span>}
</nav>
);
}Below 600 pixels the nav shows a compact Menu button, and above it the full links. Resizing the window across the breakpoint swaps the two without a page reload. The returned boolean stays current without a resize handler, and the same Hook can drive any number of layout decisions in one component.
Mind the query syntax and the server
Media features must be wrapped in parentheses, so (width <= 600px) works while width <= 600px does not. The Hook reads window during render, so it targets client-rendered apps. For server rendering, use useSyncExternalStore with a getServerSnapshot function that returns a stable initial value.
Track several breakpoints
Call useMediaQuery once per breakpoint and name the results. A dashboard can compute isTablet and isDesktop side by side, then choose its layout from those booleans. Each call keeps its own subscription, so the listeners are cleaned up independently.
Prefer CSS when it can do the job
For styling changes, a plain CSS media query is simpler and cheaper than JavaScript state. Reach for useMediaQuery only when behavior must change, such as swapping markup or deciding what a component renders. If several components need the same breakpoint, wrap the shared logic once in the Hook instead of repeating matchMedia calls. JavaScript media queries exist for the cases CSS alone cannot express in markup.
See how to create a custom Hook for the extraction steps, and how to synchronize React with browser APIs for the subscription pattern behind it.
Rune AI
Key Insights
- Read the initial matches value with a lazy useState initializer.
- Subscribe to the MediaQueryList change event, not resize.
- Remove the listener in the Effect cleanup.
- Wrap media features in parentheses, like (width <= 600px).
- Use useSyncExternalStore when server rendering matters.
Frequently Asked Questions
What does matchMedia return?
Why subscribe to the change event instead of resize?
Does this Hook work during server rendering?
Conclusion
A useMediaQuery Hook wraps window.matchMedia so a component can re-render when a breakpoint flips. Read the initial value lazily, subscribe to the change event, and clean up the listener when the query changes or the component unmounts.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.