Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
import { useState, useEffect, useCallback } from "react";
/**
* useMediaQuery
* Retorna `true` quando a media query CSS dada corresponde.
* Seguro para SSR: retorna `defaultValue` no 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 conveniência construídos 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)");
}Quando usar isso: Você precisa de comportamento responsivo em JavaScript que o CSS sozinho não consegue lidar, como renderizar componentes condicionalmente, carregar dados diferentes ou ajustar parâmetros de hook com base no tamanho da tela.
"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>
);
}O que isso demonstra:
useIsMobile renderiza condicionalmente um menu hambúrguer em vez de navegação desktop.usePrefersDarkMode aplica um tema sem nenhuma troca de classe CSS.usePrefersReducedMotion desabilita transições CSS para usuários que preferem movimento reduzido.window.matchMedia cria um objeto MediaQueryList que avalia uma string de media query CSS.change dispara sempre que o estado da correspondência muda (por exemplo, a janela cruza um breakpoint), acionando uma atualização de estado.defaultValue quando window não está disponível. O efeito só é executado no cliente.| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
query | string | - | Qualquer string de media query CSS válida |
defaultValue | boolean | false | Retornado durante SSR ou quando matchMedia não está disponível |
| Retorna | boolean | - | Se a query corresponde atualmente |
Múltiplas queries: Para lógica responsiva complexa, chame o hook várias vezes:
const isSm = useMediaQuery("(min-width: 640px)");
const isMd = useMediaQuery("(min-width: 768px)");
const isLg = useMediaQuery("(min-width: 1024px)");Objeto de breakpoint: Retorna um breakpoint nomeado para uso no 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 simples, então nenhum genérico é necessário."xs" | "sm" | "md" | "lg" | "xl".defaultValue, mas o cliente pode avaliar para um valor diferente. Isso pode causar um flash. Correção: Aceite o breve flash, ou use design responsivo baseado em CSS para elementos críticos de layout e reserve useMediaQuery para lógica não visual.useMediaQuery cria um listener matchMedia separado. Correção: Isso é aceitável para um punhado de breakpoints. Se você tiver dezenas, considere um único listener com múltiplos breakpoints.matchMedia não lança erro para queries inválidas; ele retorna um MediaQueryList que nunca corresponde. Correção: Valide queries durante o desenvolvimento.addListener/removeListener em vez de addEventListener. Correção: Safari moderno suporta a API padrão. Para suporte legado, adicione um fallback.| Pacote | Nome do Hook | Notas |
|---|---|---|
usehooks-ts | useMediaQuery | API similar, bem testado |
@uidotdev/usehooks | useMediaQuery | Implementação mínima |
ahooks | useResponsive | Retorna objeto de breakpoint |
react-responsive | useMediaQuery | Suporta renderização do lado do servidor com dicas |
| Tailwind CSS | Classes responsivas | Apenas CSS, sem necessidade de JS |
window.matchMedia(query) retorna um objeto MediaQueryList.matches e dispara um evento change quando o estado da correspondência muda.matches na montagem e se inscreve em change para atualizações ao vivo.Eles são wrappers finos que chamam useMediaQuery com uma string de query pré-construída. Por exemplo, useIsMobile(768) chama useMediaQuery("(max-width: 767px)"). Nenhuma lógica extra é adicionada.
max-width: 767px visa telas mais estreitas que 768px. Usar breakpoint - 1 garante que exatamente 768px de largura não seja considerado mobile, correspondendo às convenções comuns de breakpoint CSS.
defaultValue (false), mas o cliente pode avaliar de forma diferente.useMediaQuery para lógica não visual como busca de dados ou toggles de funcionalidade.window.matchMedia não lança erro para queries inválidas. Ele retorna um MediaQueryList que nunca corresponde. Não há validação no nível do navegador. Verifique suas strings de query durante o desenvolvimento.
Sim, cada chamada cria seu próprio listener matchMedia. Para um punhado de breakpoints, isso é aceitável. Se você tiver dezenas, consolide em um único hook useBreakpoint que retorna uma string de breakpoint nomeada.
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 o SSR, window.matchMedia não está disponível. defaultValue fornece um fallback seguro para que o hook retorne um booleano previsível no servidor. Ele tem o padrão false.
Sim. Use o hook de conveniência usePrefersReducedMotion ou passe a query diretamente:
const reducedMotion = useMediaQuery("(prefers-reduced-motion: reduce)");Revisado por Chris St. John·Última atualização: 16 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥