How to Use the useMediaQuery Hook in Astryx for Responsive Breakpoints
The useMediaQuery hook in Astryx enables React components to subscribe to CSS media queries and receive boolean values that update automatically when viewport conditions change, while providing a serverDefault parameter to ensure hydration-safe server-side rendering.
The useMediaQuery hook is a core utility exported from the @astryxdesign/core package that bridges the gap between CSS media queries and React state. Located in the facebook/astryx repository, this 53-line implementation leverages React 18's useSyncExternalStore pattern to safely integrate window.matchMedia with both client and server environments.
Core Implementation and SSR Safety
According to the Astryx source code in packages/core/src/hooks/useMediaQuery.ts, the hook implements a three-phase subscription model that ensures consistency across SSR and client-side hydration.
Subscription phase: When the component mounts, the hook creates a matchMedia object for the supplied query and registers a change event listener using mql.addEventListener('change', onStoreChange). The cleanup function removes this listener when the component unmounts.
Snapshot phase: On every render, the hook reads the current match state via window.matchMedia(query).matches to determine if the query currently matches.
Server snapshot phase: During server-side rendering where window is undefined, the hook returns the developer-provided serverDefault value instead of accessing the DOM, preventing hydration mismatches and layout shift on initial paint.
// packages/core/src/hooks/useMediaQuery.ts
export function useMediaQuery(query: string, serverDefault = false): boolean {
const subscribe = useCallback(
(onStoreChange) => {
const mql = window.matchMedia(query);
mql.addEventListener('change', onStoreChange);
return () => mql.removeEventListener('change', onStoreChange);
},
[query],
);
const getSnapshot = useCallback(() => window.matchMedia(query).matches, [query]);
const getServerSnapshot = useCallback(() => serverDefault, [serverDefault]);
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
}
Basic Breakpoint Detection
Use the useMediaQuery hook in Astryx to conditionally render content based on viewport width by passing standard CSS media query strings.
import { useMediaQuery } from '@astryxdesign/core';
export function MobileOnly() {
const isMobile = useMediaQuery('(max-width: 768px)');
if (!isMobile) return null;
return <div>This content is visible only on mobile devices.</div>;
}
The hook returns true when the viewport width is less than or equal to 768 pixels, and automatically re-renders the component when the user resizes the browser window or rotates their device.
Configuring SSR Fallbacks
When using the useMediaQuery hook in Astryx within server-side rendered applications like Next.js, always provide the serverDefault parameter to avoid flashes of incorrect layout during hydration.
// In a Next.js page component
export default function Page({ isMobileHint }: { isMobileHint: boolean }) {
const isMobile = useMediaQuery('(max-width: 768px)', isMobileHint);
return <div>{isMobile ? 'Mobile view' : 'Desktop view'}</div>;
}
// Provide the hint via getServerSideProps or similar
export async function getServerSideProps(context) {
const ua = context.req.headers['user-agent'] || '';
const isMobileHint = /Mobi|Android/i.test(ua);
return { props: { isMobileHint } };
}
This pattern ensures the initial HTML rendered on the server matches the expected mobile or desktop layout, eliminating content-shift flicker that occurs when the client re-evaluates the query after hydration.
Combining Multiple Breakpoints
For complex responsive layouts, compose multiple useMediaQuery calls to handle tablet, desktop, and mobile breakpoints simultaneously.
export function ResponsiveLayout() {
const isTablet = useMediaQuery('(min-width: 769px) and (max-width: 1024px)');
const isDesktop = useMediaQuery('(min-width: 1025px)');
if (isTablet) return <TabletLayout />;
if (isDesktop) return <DesktopLayout />;
return <MobileLayout />;
}
Each hook operates independently and only triggers re-renders when its specific query boundary is crossed, ensuring optimal performance even when monitoring multiple viewport ranges.
Detecting Interaction Capabilities
Beyond viewport width, use the useMediaQuery hook in Astryx to detect device capabilities such as hover support, which powers accessible styling decisions.
export function HoverAwareButton() {
const canHover = useMediaQuery('(hover: hover)');
return (
<button
style={{
cursor: canHover ? 'pointer' : 'default',
backgroundColor: canHover ? '#0064E0' : '#ccc',
}}
>
Click me
</button>
);
}
This same pattern is implemented in packages/core/src/theme/useTheme.ts to detect system color scheme preferences via (prefers-color-scheme: dark), and in packages/core/src/hooks/useMenuHover.ts to conditionally enable hover-based interactions.
Integration with Astryx Theme System
The useMediaQuery hook serves as the foundation for higher-level Astryx utilities. In packages/core/src/theme/useTheme.ts, the hook monitors (prefers-color-scheme: dark) to automatically switch between light and dark modes. Similarly, packages/core/src/hooks/useMenuHover.ts uses the hook to detect fine pointer capabilities before attaching expensive mouse event listeners.
The hook is publicly exported from packages/core/src/hooks/index.ts, making it available via import { useMediaQuery } from '@astryxdesign/core'.
Summary
- The
useMediaQueryhook in Astryx wrapswindow.matchMediain a React 18useSyncExternalStorepattern to provide reactive boolean values for CSS media queries. - Always provide a
serverDefaultwhen rendering on the server to prevent hydration mismatches and layout shift. - The hook accepts any valid CSS media query string, enabling detection of viewport size, color scheme, hover capabilities, and other media features.
- Source files to reference: Implementation lives in
packages/core/src/hooks/useMediaQuery.ts, with consumption examples inpackages/core/src/theme/useTheme.tsandpackages/core/src/hooks/useMenuHover.ts.
Frequently Asked Questions
How does useMediaQuery handle server-side rendering?
The useMediaQuery hook accepts a serverDefault parameter that defaults to false. During SSR, when window is undefined, the hook returns this default value instead of attempting to access matchMedia. After hydration, React replaces the server value with the actual client-side media query result, and the component re-renders if they differ.
What is the performance impact of using multiple useMediaQuery hooks?
Each useMediaQuery instance creates its own matchMedia listener, but the browser optimizes multiple listeners for the same query. For distinct queries (like separate breakpoints for mobile, tablet, and desktop), the hook is efficient because it uses useSyncExternalStore, which prevents unnecessary re-renders through React's selective hydration and snapshot mechanisms.
Can I use useMediaQuery for features other than viewport width?
Yes, the useMediaQuery hook in Astryx accepts any valid CSS media query string. Common use cases include detecting (prefers-color-scheme: dark) for theme switching, (hover: hover) for input capability detection, (orientation: landscape) for rotation handling, and prefers-reduced-motion for accessibility preferences.
Where is the hook exported from in the Astryx package?
The useMediaQuery function is defined in packages/core/src/hooks/useMediaQuery.ts and re-exported from packages/core/src/hooks/index.ts as part of the public API. You can import it using import { useMediaQuery } from '@astryxdesign/core'.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →