Runnable lesson · press Try it on the examples to edit and run them here.
A responsive component adapts to the space it has. Almost all of this is a CSS job, and React only needs to help when the content or structure must change.
Start with CSS
Fluid layouts, flexible grids and media queries need no JavaScript:
function App() {
return (
<div>
<style>{`
.grid { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }
.card { padding: 16px; background: #eaf2f8; border-radius: 8px; }
@media (max-width: 420px) {
.card { background: #fdebd0; }
}
`}</style>
<p>Drag the corner of the preview frame to make it narrower or wider.</p>
<div className="grid">
<div className="card">Card 1</div>
<div className="card">Card 2</div>
<div className="card">Card 3</div>
<div className="card">Card 4</div>
</div>
</div>
);
}
auto-fit with minmax gives as many columns as fit, with no breakpoints at all. The media query changes the background on narrow frames. (Try the layout lessons in CSS Grid and Responsive design from the CSS course for more.)
Container queries: respond to the component’s own width
A component does not know the screen width of where it will be placed. Container queries let it respond to its container’s width, which suits reusable components:
function App() {
return (
<div>
<style>{`
.wrap { container-type: inline-size; resize: horizontal; overflow: auto; border: 1px dashed #aaa; padding: 8px; max-width: 100%; width: 420px; }
.product { display: grid; gap: 8px; }
@container (min-width: 360px) {
.product { grid-template-columns: 80px 1fr; align-items: center; }
}
.thumb { background: #d5d8dc; height: 80px; border-radius: 6px; }
`}</style>
<div className="wrap">
<div className="product">
<div className="thumb" />
<div>
<strong>Product</strong>
<p style={{ margin: 0 }}>Stacks when its box is narrow, sits side by side when wide.</p>
</div>
</div>
</div>
</div>
);
}
Drag the dashed box’s bottom-right corner. Container queries work in current versions of all major browsers; check browser support for your audience.
When JavaScript is needed
Sometimes the markup itself differs: a full menu on desktop and a “hamburger” on mobile, or fewer table columns. For that you can read the viewport in React. The cleanest tool is matchMedia through a small hook:
function useMediaQuery(query) {
const [matches, setMatches] = React.useState(() => window.matchMedia(query).matches);
React.useEffect(() => {
const mql = window.matchMedia(query);
const onChange = (e) => setMatches(e.matches);
mql.addEventListener("change", onChange);
setMatches(mql.matches);
return () => mql.removeEventListener("change", onChange);
}, [query]);
return matches;
}
function App() {
const isNarrow = useMediaQuery("(max-width: 480px)");
return (
<div>
<p>Layout: {isNarrow ? "compact (menu button)" : "wide (full menu)"}</p>
{isNarrow ? <button>Menu</button> : <nav>Home | Products | About</nav>}
</div>
);
}
The hook listens for changes and updates state, and it cleans up its listener (Cleanup Functions). It measures the frame in the playground; in your app it measures the browser window. Resize the preview to see it switch.
Two cautions:
- Do not use JavaScript for what CSS can do. Hiding something with
display: nonein a media query is cheaper and flicker-free. - Server rendering:
windowdoes not exist on the server, so hooks like this need care in frameworks.
Responsive images and text
max-width: 100%; height: autokeeps images inside their box.- Use
srcSetandsizeson<img>(camelCase in JSX) to give the browser several image sizes. - Fluid type with
clamp():font-size: clamp(1rem, 2vw + 0.5rem, 1.5rem). - Always include
<meta name="viewport" content="width=device-width, initial-scale=1.0">inindex.html; the Vite template does.
Testing responsiveness
Use the browser’s device toolbar and also test on real phones with npm run dev -- --host (Running the Dev Server). Check text zoom and landscape too.
Key points
- Make components responsive with CSS (fluid grids, media and container queries) first.
- Use a
matchMediahook only when the rendered structure must change. - Container queries suit reusable components, because they respond to the component’s own space.