useMediaQuery
The useMediaQuery hook evaluates CSS media queries in React and returns a boolean indicating whether the query matches the current viewport. It automatically updates when the viewport changes.
Installation
npm install @craft-ui/hooks
import { useMediaQuery } from "@craft-ui/hooks";
import { useMediaQuery } from "@craft-ui/hooks";
function ResponsiveComponent() {
const isDesktop = useMediaQuery("(min-width: 1024px)");
return (
<div>
{isDesktop ? (
<div>Desktop Layout</div>
) : (
<div>Mobile/Tablet Layout</div>
)}
</div>
);
}
Parameters
A CSS media query string to evaluate (e.g., "(min-width: 768px)", "(prefers-color-scheme: dark)").
Returns
A boolean value indicating whether the media query currently matches. Updates automatically when the viewport or media conditions change.
Type Definition
function useMediaQuery(query: string): boolean;
Examples
Breakpoint Detection
import { useMediaQuery } from "@craft-ui/hooks";
function BreakpointExample() {
const isMobile = useMediaQuery("(max-width: 767px)");
const isTablet = useMediaQuery("(min-width: 768px) and (max-width: 1023px)");
const isDesktop = useMediaQuery("(min-width: 1024px)");
return (
<div>
<p>Current device:</p>
{isMobile && <p>Mobile</p>}
{isTablet && <p>Tablet</p>}
{isDesktop && <p>Desktop</p>}
</div>
);
}
Dark Mode Detection
import { useMediaQuery } from "@craft-ui/hooks";
import { useEffect } from "react";
function ThemeDetector() {
const prefersDark = useMediaQuery("(prefers-color-scheme: dark)");
useEffect(() => {
document.documentElement.classList.toggle("dark", prefersDark);
}, [prefersDark]);
return (
<div>
<p>Current theme: {prefersDark ? "Dark" : "Light"}</p>
</div>
);
}
Responsive Grid Layout
import { useMediaQuery } from "@craft-ui/hooks";
function ResponsiveGrid({ items }) {
const isLarge = useMediaQuery("(min-width: 1280px)");
const isMedium = useMediaQuery("(min-width: 768px) and (max-width: 1279px)");
const columns = isLarge ? 4 : isMedium ? 3 : 1;
return (
<div
style={{
display: "grid",
gridTemplateColumns: `repeat(${columns}, 1fr)`,
gap: "1rem",
}}
>
{items.map(item => (
<div key={item.id}>{item.content}</div>
))}
</div>
);
}
Orientation Detection
import { useMediaQuery } from "@craft-ui/hooks";
function OrientationAware() {
const isPortrait = useMediaQuery("(orientation: portrait)");
const isLandscape = useMediaQuery("(orientation: landscape)");
return (
<div>
{isPortrait && <p>Please rotate your device to landscape</p>}
{isLandscape && <VideoPlayer />}
</div>
);
}
Reduced Motion Preference
import { useMediaQuery } from "@craft-ui/hooks";
function AnimatedComponent() {
const prefersReducedMotion = useMediaQuery("(prefers-reduced-motion: reduce)");
return (
<div
style={{
transition: prefersReducedMotion ? "none" : "all 0.3s ease",
}}
className="animated-element"
>
{/* Content */}
</div>
);
}
Hover Capability Detection
import { useMediaQuery } from "@craft-ui/hooks";
function InteractiveCard() {
const canHover = useMediaQuery("(hover: hover) and (pointer: fine)");
return (
<div
className="card"
style={{
cursor: canHover ? "pointer" : "default",
}}
>
{canHover ? (
<p>Hover over me for effects</p>
) : (
<p>Tap to interact</p>
)}
</div>
);
}
Print Styles
import { useMediaQuery } from "@craft-ui/hooks";
function PrintableDocument() {
const isPrint = useMediaQuery("print");
return (
<div>
{!isPrint && <Navigation />}
<article>
<h1>Article Title</h1>
<p>Content...</p>
</article>
{!isPrint && <Footer />}
</div>
);
}
Multiple Breakpoints
import { useMediaQuery } from "@craft-ui/hooks";
function MultiBreakpoint() {
const breakpoints = {
xs: useMediaQuery("(max-width: 639px)"),
sm: useMediaQuery("(min-width: 640px) and (max-width: 767px)"),
md: useMediaQuery("(min-width: 768px) and (max-width: 1023px)"),
lg: useMediaQuery("(min-width: 1024px) and (max-width: 1279px)"),
xl: useMediaQuery("(min-width: 1280px)"),
};
const getLayout = () => {
if (breakpoints.xl) return "xl-layout";
if (breakpoints.lg) return "lg-layout";
if (breakpoints.md) return "md-layout";
if (breakpoints.sm) return "sm-layout";
return "xs-layout";
};
return <div className={getLayout()}>{/* Content */}</div>;
}
Conditional Feature Loading
import { useMediaQuery } from "@craft-ui/hooks";
import { lazy, Suspense } from "react";
const DesktopFeature = lazy(() => import("./DesktopFeature"));
function ConditionalFeature() {
const isDesktop = useMediaQuery("(min-width: 1024px)");
return (
<div>
{isDesktop ? (
<Suspense fallback={<div>Loading...</div>}>
<DesktopFeature />
</Suspense>
) : (
<MobileAlternative />
)}
</div>
);
}
Common Media Queries
Breakpoints
const isMobile = useMediaQuery("(max-width: 767px)");
const isTablet = useMediaQuery("(min-width: 768px) and (max-width: 1023px)");
const isDesktop = useMediaQuery("(min-width: 1024px)");
User Preferences
const prefersDark = useMediaQuery("(prefers-color-scheme: dark)");
const prefersLight = useMediaQuery("(prefers-color-scheme: light)");
const prefersReducedMotion = useMediaQuery("(prefers-reduced-motion: reduce)");
Device Capabilities
const canHover = useMediaQuery("(hover: hover)");
const hasCoarsePointer = useMediaQuery("(pointer: coarse)");
const hasFinePointer = useMediaQuery("(pointer: fine)");
Display
const isRetina = useMediaQuery("(-webkit-min-device-pixel-ratio: 2)");
const isPortrait = useMediaQuery("(orientation: portrait)");
const isLandscape = useMediaQuery("(orientation: landscape)");
Common Patterns
Creating a Custom Hook
import { useMediaQuery } from "@craft-ui/hooks";
function useBreakpoint() {
const isMobile = useMediaQuery("(max-width: 767px)");
const isTablet = useMediaQuery("(min-width: 768px) and (max-width: 1023px)");
const isDesktop = useMediaQuery("(min-width: 1024px)");
return { isMobile, isTablet, isDesktop };
}
// Usage
function Component() {
const { isMobile, isTablet, isDesktop } = useBreakpoint();
// ...
}
Combining Multiple Queries
function AdaptiveComponent() {
const isSmallScreen = useMediaQuery("(max-width: 767px)");
const prefersDark = useMediaQuery("(prefers-color-scheme: dark)");
const className = `
${isSmallScreen ? "small" : "large"}
${prefersDark ? "dark" : "light"}
`;
return <div className={className}>{/* Content */}</div>;
}
- The hook uses
window.matchMedia() which is supported in all modern browsers
- The media query listener is automatically added and cleaned up
- The hook returns
false initially during server-side rendering
- The initial value is set synchronously to match the current viewport
- Event listeners are properly cleaned up when the component unmounts or query changes
- Complex media queries are fully supported (combining multiple conditions with
and, or, not)
- For better performance with multiple queries, consider memoizing or creating custom hooks