Pilot, your spaceship must work just as well on a small handheld terminal screen as it does on the large monitor of a command center. In the React world, responsive design means creating components that elegantly adapt to any screen size -- from mobile devices to ultra-wide monitors.
Sometimes you need to change not just styles but the entire component structure depending on screen size. That's when a custom
useMediaQuery hook comes in handy:1function useMediaQuery(query) {
2 const [matches, setMatches] = useState(
3 () => window.matchMedia(query).matches
4 );
5
6 useEffect(() => {
7 const mediaQuery = window.matchMedia(query);
8 const handler = (e) => setMatches(e.matches);
9
10 mediaQuery.addEventListener('change', handler);
11 return () => mediaQuery.removeEventListener('change', handler);
12 }, [query]);
13
14 return matches;
15}Usage in a component:
1function MissionDashboard() {
2 const isMobile = useMediaQuery('(max-width: 767px)');
3 const isTablet = useMediaQuery('(min-width: 768px) and (max-width: 1023px)');
4
5 if (isMobile) return <MobileDashboard />;
6 if (isTablet) return <TabletDashboard />;
7 return <DesktopDashboard />;
8}The hook listens for window size changes and returns
true/false -- React automatically re-renders the component when the value changes.The mobile-first approach means: we start with styles for the smallest screens and then expand for larger ones. Like designing a cockpit from the simplest terminal upward:
1/* Base styles -- mobile */
2.grid {
3 display: grid;
4 grid-template-columns: 1fr;
5 gap: 16px;
6}
7
8/* Tablet -- expanding */
9@media (min-width: 768px) {
10 .grid {
11 grid-template-columns: repeat(2, 1fr);
12 }
13}
14
15/* Desktop -- even wider */
16@media (min-width: 1024px) {
17 .grid {
18 grid-template-columns: repeat(3, 1fr);
19 }
20}Instead of one huge component with many conditions, it's better to split the UI into smaller parts that can be composed differently:
1function Sidebar({ items }) {
2 return (
3 <nav className="sidebar">
4 {items.map(item => (
5 <a key={item.id} href={item.href}>{item.label}</a>
6 ))}
7 </nav>
8 );
9}
10
11function BottomNav({ items }) {
12 return (
13 <nav className="bottom-nav">
14 {items.map(item => (
15 <a key={item.id} href={item.href}>{item.icon}</a>
16 ))}
17 </nav>
18 );
19}
20
21function Navigation({ items }) {
22 const isMobile = useMediaQuery('(max-width: 767px)');
23 return isMobile ? <BottomNav items={items} /> : <Sidebar items={items} />;
24}On mobile, the navigation is at the bottom of the screen (like in an app), and on desktop -- on the side as a sidebar. Same data set, different presentation.
Container queries are a powerful new CSS feature. They allow you to style an element based on the size of its parent, not the entire browser window:
1.card-container {
2 container-type: inline-size;
3}
4
5@container (min-width: 400px) {
6 .card {
7 display: flex;
8 gap: 16px;
9 }
10}
11
12@container (min-width: 600px) {
13 .card {
14 grid-template-columns: 1fr 2fr;
15 }
16}In React, we set
container-type on the parent:1function CardGrid({ children }) {
2 return (
3 <div style={{ containerType: 'inline-size' }}>
4 {children}
5 </div>
6 );
7}Container queries are great for reusable components -- a mission card can look different in a narrow sidebar and a wide main content area, without knowing the window size.
Instead of changing font size in steps between breakpoints, you can use
clamp() for smooth scaling:1.heading {
2 /* Minimum 18px, optimal 4vw, maximum 36px */
3 font-size: clamp(18px, 4vw, 36px);
4}
5
6.body-text {
7 font-size: clamp(14px, 2vw, 18px);
8 line-height: clamp(20px, 3vw, 28px);
9}In React, you can implement this as a system:
1const fluidType = {
2 heading: 'clamp(18px, 4vw, 36px)',
3 subheading: 'clamp(16px, 3vw, 24px)',
4 body: 'clamp(14px, 2vw, 18px)',
5 caption: 'clamp(11px, 1.5vw, 14px)',
6};
7
8function Heading({ children }) {
9 return <h1 style={{ fontSize: fluidType.heading }}>{children}</h1>;
10}Images are often the heaviest assets on a page. React allows you to implement responsive images:
1function ResponsiveImage({ src, alt, sizes }) {
2 return (
3 <img
4 src={src}
5 alt={alt}
6 srcSet={`${src}?w=400 400w, ${src}?w=800 800w, ${src}?w=1200 1200w`}
7 sizes={sizes || '(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw'}
8 loading="lazy"
9 style={{ width: '100%', height: 'auto' }}
10 />
11 );
12}The
srcSet attribute tells the browser about available sizes, and sizes -- which size to use at a given window size.