Documentation
Templates
Hooks
useMediaQuery

useMediaQuery

React to CSS media query changes.

Use Cases

  • Conditional rendering based on screen size
  • Adjust component behavior for mobile/desktop
  • Dark mode detection
  • Reduced motion preferences

Code

import { useState, useEffect } from 'react';
 
export function useMediaQuery(query: string): boolean {
  const [matches, setMatches] = useState<boolean>(() => {
    if (typeof window === 'undefined') {
      return false;
    }
    return window.matchMedia(query).matches;
  });
 
  useEffect(() => {
    if (typeof window === 'undefined') {
      return;
    }
 
    const mediaQuery = window.matchMedia(query);
    setMatches(mediaQuery.matches);
 
    const handler = (event: MediaQueryListEvent) => {
      setMatches(event.matches);
    };
 
    mediaQuery.addEventListener('change', handler);
 
    return () => {
      mediaQuery.removeEventListener('change', handler);
    };
  }, [query]);
 
  return matches;
}

Usage

Responsive Sidebar

function Layout({ children }) {
  const isDesktop = useMediaQuery('(min-width: 1024px)');
  const [sidebarOpen, setSidebarOpen] = useState(false);
 
  // Auto-close sidebar on mobile when navigating
  useEffect(() => {
    if (!isDesktop) {
      setSidebarOpen(false);
    }
  }, [isDesktop]);
 
  return (
    <div className="flex">
      {(isDesktop || sidebarOpen) && (
        <aside className="w-64 border-r">
          <nav>...</nav>
        </aside>
      )}
      <main className="flex-1">{children}</main>
    </div>
  );
}

Mobile Navigation

function Navigation() {
  const isMobile = useMediaQuery('(max-width: 768px)');
 
  if (isMobile) {
    return <MobileNav />;
  }
 
  return <DesktopNav />;
}

Dark Mode Detection

function ThemeProvider({ children }) {
  const prefersDark = useMediaQuery('(prefers-color-scheme: dark)');
  const [theme, setTheme] = useLocalStorage('theme', 'system');
 
  const isDark = theme === 'system' ? prefersDark : theme === 'dark';
 
  return (
    <div className={isDark ? 'dark' : ''}>
      {children}
    </div>
  );
}

Reduced Motion

function AnimatedComponent() {
  const prefersReducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)');
 
  return (
    <motion.div
      animate={{ x: 100 }}
      transition={{
        duration: prefersReducedMotion ? 0 : 0.3,
      }}
    >
      Content
    </motion.div>
  );
}

Tailwind Breakpoints Helper

Create preset hooks for Tailwind breakpoints:

// hooks/useBreakpoint.ts
export function useBreakpoint() {
  const sm = useMediaQuery('(min-width: 640px)');
  const md = useMediaQuery('(min-width: 768px)');
  const lg = useMediaQuery('(min-width: 1024px)');
  const xl = useMediaQuery('(min-width: 1280px)');
  const xxl = useMediaQuery('(min-width: 1536px)');
 
  return { sm, md, lg, xl, xxl };
}
 
// Usage
function Component() {
  const { md, lg } = useBreakpoint();
 
  const columns = lg ? 4 : md ? 2 : 1;
 
  return <Grid columns={columns}>...</Grid>;
}

Common Media Queries

QueryDescription
(min-width: 768px)Tablet and up
(min-width: 1024px)Desktop and up
(prefers-color-scheme: dark)Dark mode preference
(prefers-reduced-motion: reduce)Reduced motion preference
(orientation: portrait)Portrait orientation
(hover: hover)Device supports hover