Busca en todas las páginas de la documentación
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
import { useState, useEffect, useCallback } from "react";
/**
* useMediaQuery
* Devuelve `true` cuando la consulta de medios CSS dada coincide.
* Seguro para SSR: devuelve `defaultValue` en el servidor.
*/
function useMediaQuery(
query: string,
defaultValue: boolean = false
): boolean {
const [matches, setMatches] = useState<boolean>(() => {
if (typeof window === "undefined") return defaultValue;
return window.matchMedia(query).matches;
});
useEffect(() => {
if (typeof window === "undefined") return;
const mql = window.matchMedia(query);
setMatches(mql.matches);
const handler = (e: MediaQueryListEvent) => {
setMatches(e.matches);
};
mql.addEventListener("change", handler);
return () => mql.removeEventListener("change", handler);
}, [query]);
return matches;
}
/**
* Hooks de conveniencia construidos sobre useMediaQuery
*/
function useIsMobile(breakpoint: number = 768): boolean {
return useMediaQuery(`(max-width: ${breakpoint - 1}px)`);
}
function useIsDesktop(breakpoint: number = 1024): boolean {
return useMediaQuery(`(min-width: ${breakpoint}px)`);
}
function usePrefersDarkMode(): boolean {
return useMediaQuery("(prefers-color-scheme: dark)");
}
function usePrefersReducedMotion(): boolean {
return useMediaQuery("(prefers-reduced-motion: reduce)");
}Cuándo usarlo: Necesitas comportamiento sensible en JavaScript que solo CSS no puede manejar, como renderizar componentes condicionalmente, cargar datos diferentes o ajustar parámetros de hooks basados en el tamaño de pantalla.
"use client";
function ResponsiveNav() {
const isMobile = useIsMobile();
const prefersDark = usePrefersDarkMode();
const reducedMotion = usePrefersReducedMotion();
return (
<nav
style={{
background: prefersDark ? "#1a1a2e" : "#ffffff",
transition: reducedMotion ? "none" : "background 0.3s",
}}
>
{isMobile ? <HamburgerMenu /> : <DesktopMenu />}
</nav>
);
}
function HamburgerMenu() {
return <button aria-label="Menu">☰</button>;
}
function DesktopMenu() {
return (
<ul style={{ display: "flex", gap: 16, listStyle: "none" }}>
<li>Home</li>
<li>About</li>
<li>Contact</li>
</ul>
);
}
function AdaptiveGrid() {
const isDesktop = useIsDesktop();
const columns = isDesktop ? 3 : 1;
return (
<div
style={{
display: "grid",
gridTemplateColumns: `repeat(${columns}, 1fr)`,
gap: 16,
}}
>
<div>Card 1</div>
<div>Card 2</div>
<div>Card 3</div>
</div>
);
}Lo que esto demuestra:
useIsMobile renderiza condicionalmente un menú de hamburguesa versus navegación de escritoriousePrefersDarkMode aplica un tema sin alternancia de clases CSSusePrefersReducedMotion desactiva transiciones CSS para usuarios que prefieren movimiento reducidowindow.matchMedia crea un objeto MediaQueryList que evalúa una cadena de consulta de medios CSS.change se dispara cada vez que el estado de coincidencia cambia (por ejemplo, cuando la ventana cruza un breakpoint), disparando una actualización de estado.defaultValue cuando window no está disponible. El efecto solo se ejecuta en el cliente.| Parameter | Type | Default | Description |
|---|---|---|---|
query | string | - | Cualquier cadena de consulta de medios CSS válida |
defaultValue | boolean | false | Devuelto durante SSR o cuando matchMedia no está disponible |
| Returns | boolean | - | Si la consulta actualmente coincide |
Múltiples consultas: Para lógica sensible compleja, llama el hook múltiples veces:
const isSm = useMediaQuery("(min-width: 640px)");
const isMd = useMediaQuery("(min-width: 768px)");
const isLg = useMediaQuery("(min-width: 1024px)");Objeto de breakpoint: Devuelve un breakpoint nombrado para uso estilo Tailwind:
function useBreakpoint() {
const isSm = useMediaQuery("(min-width: 640px)");
const isMd = useMediaQuery("(min-width: 768px)");
const isLg = useMediaQuery("(min-width: 1024px)");
const isXl = useMediaQuery("(min-width: 1280px)");
if (isXl) return "xl";
if (isLg) return "lg";
if (isMd) return "md";
if (isSm) return "sm";
return "xs";
}boolean, así que no se necesitan genéricos."xs" | "sm" | "md" | "lg" | "xl".defaultValue, pero el cliente puede evaluar a un valor diferente. Esto puede causar un parpadeo. Solución: Acepta el breve parpadeo, o usa diseño sensible basado en CSS para elementos críticos de layout y reserva useMediaQuery para lógica no visual.useMediaQuery crea un listener matchMedia separado. Solución: Esto está bien para un puñado de breakpoints. Si tienes docenas, considera un único listener con múltiples breakpoints.matchMedia no lanza error para consultas inválidas; devuelve un MediaQueryList que nunca coincide. Solución: Valida consultas durante el desarrollo.addListener/removeListener en lugar de addEventListener. Solución: Safari moderno soporta la API estándar. Para soporte heredado, añade un fallback.| Package | Hook Name | Notes |
|---|---|---|
usehooks-ts | useMediaQuery | API similar, bien probado |
@uidotdev/usehooks | useMediaQuery | Implementación mínima |
ahooks | useResponsive | Devuelve objeto de breakpoint |
react-responsive | useMediaQuery | Soporta renderizado del lado del servidor con hints |
| Tailwind CSS | Responsive classes | Solo CSS, sin JS necesario |
window.matchMedia(query) devuelve un objeto MediaQueryList.matches y dispara un evento change cuando el estado de coincidencia cambia.matches al montarse y se suscribe a change para actualizaciones en vivo.Son envoltorios delgados que llaman useMediaQuery con una cadena de consulta pregenerada. Por ejemplo, useIsMobile(768) llama useMediaQuery("(max-width: 767px)"). No se añade lógica extra.
max-width: 767px se dirige a pantallas más estrechas que 768px. Usar breakpoint - 1 asegura que exactamente 768px de ancho no se considere móvil, coincidiendo con convenciones comunes de breakpoint CSS.
defaultValue (false), pero el cliente puede evaluar diferentemente.useMediaQuery para lógica no visual como obtención de datos o alternadores de características.window.matchMedia no lanza error para consultas inválidas. Devuelve un MediaQueryList que nunca coincide. No hay validación a nivel de navegador. Verifica tus cadenas de consulta durante el desarrollo.
Sí, cada llamada crea su propio listener matchMedia. Para un puñado de breakpoints esto está bien. Si tienes docenas, consolida en un único hook useBreakpoint que devuelva una cadena de nombre de breakpoint.
function useBreakpoint(): "xs" | "sm" | "md" | "lg" | "xl" {
const isSm = useMediaQuery("(min-width: 640px)");
const isMd = useMediaQuery("(min-width: 768px)");
const isLg = useMediaQuery("(min-width: 1024px)");
const isXl = useMediaQuery("(min-width: 1280px)");
if (isXl) return "xl";
if (isLg) return "lg";
if (isMd) return "md";
if (isSm) return "sm";
return "xs";
}Durante SSR, window.matchMedia no está disponible. defaultValue proporciona un fallback seguro para que el hook devuelva un booleano predecible en el servidor. Por defecto es false.
Sí. Usa el hook de conveniencia usePrefersReducedMotion o pasa la consulta directamente:
const reducedMotion = useMediaQuery("(prefers-reduced-motion: reduce)");Revisado por Chris St. John·Última actualización: 16 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥