We use cookies to enhance your experience on the site
CodeWorlds

Responsive Design Patterns in React - Adapting to Every Space

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.

useMediaQuery Hook -- responsiveness in JavaScript

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.

Mobile-first CSS in React

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}

Responsive component composition

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 in React

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.

Fluid Typography

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}

Responsive Images in React

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.

When CSS, when JavaScript?

  • Media queries in CSS -- when you're only changing styles (colors, sizes, layout)
  • useMediaQuery in JS -- when you're changing the component structure (different components, different logic)
  • Container queries -- when a component needs to react to the size of its container
  • clamp() -- for smooth scaling of values between a minimum and maximum
Go to CodeWorlds