Responsive React components change their layout at viewport breakpoints instead of shipping a separate design per device. The core idea is mobile-first: write the small-screen layout as the default, then add styles that only apply at wider breakpoints.
Start with the mobile layout
A mobile-first component uses unprefixed styles for the smallest screens. Wider breakpoints then override those styles, so the simple layout is the base and complexity is added upward.
In Tailwind, unprefixed utilities apply everywhere, while a prefixed utility such as md: only applies at the md breakpoint of 48rem and above. The default breakpoints are documented in the Tailwind responsive guide.
Add Tailwind breakpoints
A profile card stacks vertically on mobile, then lays out side by side on medium screens and larger. The default styles handle mobile, and the md: prefixes add the desktop arrangement.
function ProfileCard() {
return (
<div className="mx-auto max-w-md rounded-xl bg-white p-4 shadow-md md:flex md:max-w-2xl md:items-center md:gap-4">
<img className="h-24 w-24 rounded-full object-cover" src="/avatar.jpg" alt="Portrait of Ada" />
<div className="mt-4 text-center md:mt-0 md:text-left">
<h2 className="text-lg font-semibold text-gray-900">Ada Lovelace</h2>
<p className="text-gray-600">Computing pioneer and mathematician.</p>
</div>
</div>
);
}On a phone the image sits above a centered block of text. Past 48rem the md:flex turns the card into a row, md:items-center vertically centers the text, and md:text-left returns it to left alignment. The card still needs the setup in the Tailwind walkthrough before these classes take effect.
The same idea with CSS Modules
CSS Modules write the identical mobile-first logic as plain media queries. The default rules handle mobile, and a min-width query adds the wider layout.
/* ProfileCard.module.css */
.card {
padding: 16px;
text-align: center;
}
@media (min-width: 768px) {
.card {
display: flex;
align-items: center;
gap: 16px;
text-align: left;
}
}The component imports the module and uses the generated class name. Vite maps each class to a unique name at build time, so the styles stay scoped to this component.
import styles from "./ProfileCard.module.css";
function ProfileCard() {
return <div className={styles.card}>Profile content</div>;
}The browser applies the same breakpoint behavior as the Tailwind version. The only difference is syntax: a media query block versus prefixed utility classes.
Respond in JavaScript with a hook
CSS covers layout, but sometimes React itself must know the viewport, for example to render a different structure or pass a flag to a library. A useMediaQuery hook wraps the browser matchMedia API and returns a boolean that tracks whether a media query currently matches.
const isDesktop = useMediaQuery("(min-width: 768px)");The component can then branch on isDesktop during render. Building the hook correctly, including cleanup of the media query listener, is covered in the useMediaQuery hook article.
Container queries for component-level responsiveness
Breakpoints respond to the viewport, but a reusable component often cares about the space its parent gives it. Tailwind v4 adds container queries with the @container class and variants such as @md: that activate based on the container width instead of the page width.
Container queries make a card respond to its own column, which is useful when the same component appears in a sidebar and a wide hero. They are a newer CSS feature, so confirm browser support before relying on them alone.
Keep it accessible
Responsive design should reflow content without hiding it. Text must remain readable at every width, touch targets should stay large enough to tap, and a layout change should not remove keyboard or screen reader access.
The same class-based thinking extends to dark mode, where a single toggle restyles every component.
Rune AI
Key Insights
- Write the mobile layout as the default styles.
- Add Tailwind breakpoints with prefixes like md: and lg:.
- Use media queries in CSS Modules for the same effect in plain CSS.
- Use a useMediaQuery hook only when JavaScript needs the breakpoint.
- Container queries adapt a component to its parent width.
Frequently Asked Questions
Should I use a media query or a JavaScript hook?
Do Tailwind breakpoints replace CSS media queries?
What is mobile-first in React?
Conclusion
Responsive React components start mobile-first and add breakpoints that only activate on wider screens. Tailwind prefixes and CSS Modules media queries both compile to the same media queries, and a JavaScript hook is for the few cases where React must know the viewport.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.