Build a hook that tells a component whether a CSS media query currently matches — and keeps that answer up to date. The browser exposes window.matchMedia, which evaluates a query like (min-width: 768px) and returns a MediaQueryList whose .matches is a boolean. The catch: that boolean is a snapshot. When the user resizes the window, rotates the device, or flips their system to dark mode, the value can change — but only code that subscribes to the list's change event finds out. useMediaQuery(query) reads the current match and re-renders the component whenever the match flips.
function useMediaQuery(query: string): boolean;
The query is any valid CSS media query string. The hook returns true when it matches right now and false otherwise, updating on every change.
function Layout() {
const isWide = useMediaQuery('(min-width: 768px)');
// Re-renders true→false→true as the window crosses 768px.
return <nav>{isWide ? <DesktopNav /> : <MobileNav />}</nav>;
}
function Theme() {
const prefersDark = useMediaQuery('(prefers-color-scheme: dark)');
// Flips when the user changes their OS theme, with no resize involved.
return <div className={prefersDark ? 'dark' : 'light'}>...</div>;
}
matchMedia(query).matches, not false-by-default that corrects itself a tick later.change event, the hook must re-render with the new boolean. A version that reads .matches once and stops is wrong.query changes. If the component re-renders with a different query string, drop the old subscription and subscribe to the new list.change listener on unmount and before re-subscribing, so a stale list can't push updates into a gone or re-queried component.window.matchMedia exists. Don't worry about SSR or unsupported browsers here — that's noted in Going further.