Skip to content

React Tutorial Styling React Applications

Responsive Components in React with CSS and Hooks

Lesson 86 of 156 2 min read By Deepak Paswan Updated Oct 10, 2026

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:

App.jsx
JSX
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:

App.jsx
JSX
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:

App.jsx
JSX
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: none in a media query is cheaper and flicker-free.
  • Server rendering: window does not exist on the server, so hooks like this need care in frameworks.

Responsive images and text

  • max-width: 100%; height: auto keeps images inside their box.
  • Use srcSet and sizes on <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"> in index.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 matchMedia hook only when the rendered structure must change.
  • Container queries suit reusable components, because they respond to the component’s own space.