ScreenshotNeo

BlogHow-to

How to Make a Website Responsive with CSS

Learn a practical, mobile-first CSS workflow with fluid layouts, Flexbox, Grid, responsive media, breakpoints, accessibility, and testing.

By the ScreenshotNeo team29 September 202611 min read

How to Make a Website Responsive with CSS

Responsive web design means a page adapts to the space and conditions available instead of assuming one fixed screen size. The most reliable CSS approach is to start with a flexible narrow layout, let normal flow, Flexbox, and Grid distribute space, then add media queries only where the content needs a different arrangement. MDN describes responsive design as an approach for making pages work across different screen sizes and resolutions.

This guide builds a responsive page from a fluid baseline. It covers the viewport declaration, flexible sizing, Flexbox, Grid, content-driven breakpoints, responsive images, accessibility preferences, testing, common failures, and a production checklist. At the end, you can use ScreenshotNeo to capture responsive pages without maintaining your own browser automation.

1. Add the viewport declaration

Put this in the <head> of every responsive page:

<meta name="viewport" content="width=device-width, initial-scale=1">

The declaration tells mobile browsers to use the device width as the layout viewport. Without it, a phone may lay out the page using a much wider desktop-like viewport and then shrink the result, making your carefully written media queries appear ineffective.

2. Build a fluid baseline first

Start with normal document flow and a content column that can shrink on small screens and stop growing on very wide screens. Avoid a page wrapper such as width: 1200px; it creates horizontal scrolling on narrow screens and excessive empty space on wide ones.

/* base.css */
*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  color-scheme: light;
}

body {
  margin: 0;
  font-family: system-ui, sans-serif;
  line-height: 1.5;
  color: #172033;
  background: #f7f8fb;
}

.page {
  width: min(100% - 2rem, 70rem);
  margin-inline: auto;
}

.site-header,
.site-main,
.site-footer {
  padding-block: 1.25rem;
}

h1,
h2,
h3,
p {
  margin-block: 0 1rem;
}

.flow > * + * {
  margin-block-start: 1rem;
}

box-sizing: border-box makes padding and borders part of an element’s declared width, which prevents many accidental overflows. The min() expression keeps the page 1rem from each edge on small screens and limits the reading area to 70rem on larger ones. Adjust the values to your content; they are a starting pattern, not universal measurements.

3. Use Flexbox for one-dimensional relationships

Flexbox is a good fit when items primarily need to share space in one row or one column: a header with a logo and actions, a button group, or a row of metadata. Let items shrink and wrap instead of forcing them into a fixed width.

Flexible layout tools let content rearrange as the available space changes.
Flexible layout tools let content rearrange as the available space changes.
.site-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

.nav-list {
  display: flex;
  flex-wrap: wrap;
  gap: .75rem 1rem;
  padding: 0;
  margin: 0;
  list-style: none;
}

.toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: .75rem;
}

.toolbar > * {
  flex: 1 1 12rem;
}

flex-wrap: wrap lets navigation and controls move to another line when the available width becomes too small. flex: 1 1 12rem gives controls a useful minimum basis while allowing them to grow or shrink. If a single control must never shrink, set flex: 0 0 auto, but check that the combined controls still fit.

4. Use Grid for two-dimensional page structure

CSS Grid expresses rows and columns together. A common responsive pattern is one column by default, then a main content area and an aside when the content can support both.

.content-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.5rem;
}

@media (width >= 48rem) {
  .content-grid {
    grid-template-columns: minmax(0, 2fr) minmax(14rem, 1fr);
    align-items: start;
  }
}

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
  gap: 1rem;
}

.card {
  min-width: 0;
  padding: 1rem;
  background: white;
  border: 1px solid #dfe3ec;
  border-radius: .75rem;
}

minmax(0, 2fr) is useful for text-heavy columns because the zero minimum permits the track to shrink instead of overflowing because of a long word or unbreakable child. auto-fit fills each row with as many tracks as fit; when there is not enough room for a 16rem card, the cards naturally become one column.

5. Choose breakpoints where the content fails

A breakpoint is a conditional change, not a device model. Resize the page gradually and note when the navigation wraps awkwardly, the text measure becomes too wide, a sidebar makes the main column cramped, or controls become hard to use. Put the breakpoint at that failure point.

Prefer relative units such as rem for breakpoint values. The example below is illustrative:

@media (width >= 48rem) {
  .content-grid {
    grid-template-columns: minmax(0, 2fr) minmax(14rem, 1fr);
  }
}

@media (width >= 70rem) {
  .page {
    width: min(100% - 3rem, 80rem);
  }
}

Do not begin with separate rules for every phone and tablet. First ask whether normal flow, flexible units, Flexbox, Grid, or wrapping already solves the problem. Add a media query when the relationship or hierarchy genuinely needs to change.

6. Make typography and controls adapt

Text should remain readable without forcing users to zoom or scroll sideways. Use relative dimensions for spacing and type, constrain very long lines, and allow interactive elements to wrap.

:root {
  font-size: 100%;
}

.reading-column {
  max-width: 70ch;
}

.hero-title {
  font-size: clamp(2rem, 6vw, 4.5rem);
  line-height: 1.05;
}

button,
input,
select,
textarea {
  font: inherit;
  max-width: 100%;
}

button,
.button {
  min-block-size: 2.75rem;
  padding-inline: 1rem;
}

clamp() lets a heading grow with the viewport while enforcing a minimum and maximum. A max-width near 70 characters is a useful readability guard for prose. Keep labels visible where possible; hiding important text simply to save space can make a responsive interface less usable.

7. Keep images, video, and embeds inside their containers

CSS can stop media from escaping its container:

img,
picture,
video,
svg,
canvas {
  display: block;
  max-width: 100%;
  height: auto;
}

iframe {
  display: block;
  width: 100%;
  max-width: 100%;
  border: 0;
}

.video-wrapper {
  aspect-ratio: 16 / 9;
  overflow: hidden;
}

.video-wrapper iframe {
  height: 100%;
}

max-width: 100% prevents a source from being wider than its containing block, while height: auto preserves its intrinsic ratio. CSS resizing does not choose a more efficient source file. If a large desktop image is wasteful on a phone, provide source selection with srcset and sizes:

<img
  src="photo-1200.jpg"
  srcset="photo-480.jpg 480w,
          photo-800.jpg 800w,
          photo-1200.jpg 1200w"
  sizes="(width >= 60rem) 50vw, 100vw"
  alt="A team reviewing a responsive layout"
  width="1200"
  height="800"
>

Use <picture> when different crops or formats are appropriate:

<picture>
  <source media="(width < 40rem)" srcset="portrait-crop.webp">
  <source type="image/avif" srcset="landscape.avif">
  <img src="landscape.jpg" alt="A responsive image example">
</picture>

8. Add environment and accessibility queries

Responsive behavior includes more than viewport width. Media queries can respond to pointer capability, color scheme, contrast preferences, and user motion preferences. The most common accessibility requirement is reduced motion.

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    scroll-behavior: auto;
    animation-duration: 0.01ms;
    animation-iteration-count: 1;
    transition-duration: 0.01ms;
  }
}

@media (hover: hover) and (pointer: fine) {
  .card:hover {
    translate: 0 -2px;
  }
}

The reduced-motion rule removes or shortens nonessential movement when a user requests less motion. Preserve meaningful state changes and ensure the interface remains understandable without animation.

9. A complete responsive page example

The following small page combines the patterns above. It starts as one column, wraps the navigation, and adds a sidebar only when the content has room.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Responsive article</title>
  <style>
    *, *::before, *::after { box-sizing: border-box; }
    body { margin: 0; font: 1rem/1.5 system-ui, sans-serif; color: #172033; }
    .page { width: min(100% - 2rem, 70rem); margin-inline: auto; }
    .header { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 1rem; padding-block: 1rem; }
    .nav { display: flex; flex-wrap: wrap; gap: .75rem 1rem; }
    .layout { display: grid; gap: 1.5rem; padding-block: 2rem; }
    .cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr)); gap: 1rem; }
    .card { padding: 1rem; border: 1px solid #dfe3ec; border-radius: .75rem; }
    img { display: block; max-width: 100%; height: auto; }
    @media (width >= 48rem) {
      .layout { grid-template-columns: minmax(0, 2fr) minmax(14rem, 1fr); }
    }
    @media (prefers-reduced-motion: reduce) {
      *, *::before, *::after { scroll-behavior: auto; animation-duration: .01ms; transition-duration: .01ms; }
    }
  </style>
</head>
<body>
  <div class="page">
    <header class="header">
      <strong>Example site</strong>
      <nav class="nav" aria-label="Primary">
        <a href="#articles">Articles</a>
        <a href="#about">About</a>
        <a href="#contact">Contact</a>
      </nav>
    </header>
    <main class="layout">
      <section id="articles">
        <h1>Responsive content</h1>
        <div class="cards">
          <article class="card">First card</article>
          <article class="card">Second card</article>
          <article class="card">Third card</article>
        </div>
      </section>
      <aside id="about" class="card">Supporting content</aside>
    </main>
  </div>
</body>
</html>

10. Verify responsiveness systematically

  1. Open the page at a narrow width and confirm that no horizontal scrollbar appears.
  2. Resize gradually instead of checking only preset phone and desktop sizes. Record where text, navigation, cards, or controls become cramped.
  3. Check wide screens for lines that become too long or layouts that leave excessive unused space.
  4. Inspect long words, URLs, tables, code blocks, and user-generated content. These often expose overflow that ordinary copy does not.
  5. Test images, videos, iframes, SVGs, and third-party embeds inside their real containers.
  6. Use browser responsive sizing tools to emulate widths and device conditions, then test keyboard navigation and zoom.
  7. Enable a reduced-motion preference when animations exist and verify that content and state changes remain clear.

11. Troubleshooting common CSS failures

Symptom Likely cause Fix
Horizontal scrolling on phones Fixed width, oversized media, long unbroken text, or a grid track with an automatic minimum. Use fluid widths, max-width: 100%, overflow-wrap: anywhere for untrusted strings, and minmax(0, 1fr).
Media queries never apply Missing or incorrect viewport declaration, invalid query syntax, or a more specific rule wins. Add the viewport tag, inspect the rule in DevTools, and check computed styles and specificity.
Cards overflow a Grid column A child has an intrinsic minimum width such as a long URL or a wide table. Set the track to minmax(0, 1fr) and constrain the child with max-width: 100%.
Header wraps badly Items are forced into one line or have fixed widths. Use Flexbox with flex-wrap: wrap, a gap, and flexible item bases.
Images look blurry One small source is being enlarged, or the wrong source is selected. Provide suitable intrinsic dimensions and use srcset, sizes, or picture.
Layout works only at one width Breakpoints were chosen for devices rather than content. Resize continuously and move the breakpoint to the point where the relationship actually needs to change.
Animation causes discomfort The page ignores the user’s reduced-motion preference. Add a prefers-reduced-motion: reduce query and remove nonessential movement.

12. Performance, reliability, and maintenance

  • Prefer intrinsic layout rules over JavaScript width checks. Fewer moving parts reduce resize bugs and hydration mismatches.
  • Choose image sources with srcset and sizes so a phone does not routinely download a desktop asset.
  • Keep selectors understandable. A small number of content-driven breakpoints is easier to maintain than device-specific overrides.
  • Reserve space for media with width and height attributes or aspect-ratio to reduce layout shifts.
  • Test real content, including translated labels, long names, empty states, validation errors, and user-generated URLs.
  • When a component is reused in a narrow sidebar, modal, or grid cell, test its container width rather than assuming the viewport tells the whole story.

CSS container queries can be useful when a component’s layout should respond to its own container, while viewport media queries remain appropriate for page-level changes and user preferences. Choose the condition that represents the design decision.

ScreenshotNeo removes common consent banners, popups, and chat widgets before capture.
ScreenshotNeo removes common consent banners, popups, and chat widgets before capture.

Or skip the browser setup

If you need screenshots of responsive pages for documentation, visual checks, or generated reports, ScreenshotNeo provides a website screenshot API. It accepts one GET request and returns PNG, JPEG, WebP, or PDF output. The API can capture full pages, load lazy images, target one CSS-selected element, emulate dark mode and device presets, set any viewport and retina scale, inject custom CSS or JavaScript, click before capture, hide selectors, wait for a selector, delay, or network idle, and block selected ads, trackers, requests, or resource types.

See the ScreenshotNeo API documentation for the complete parameter list. A minimal request looks like this:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const buffer = Buffer.from(await res.arrayBuffer());
require('fs').writeFileSync('shot.webp', buffer);

ScreenshotNeo accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the result with X-Page-Verdict and X-Billed headers. It also offers an MCP server with take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.

For repeated captures, use a chosen cache TTL, signed links for public <img> tags, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, custom headers, cookies, user agents, Authorization, timezone and geolocation, PDF paper settings and page ranges, HTML/CSS-to-image, and the usage API. Plans include 1,000 shots per month free with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account to start.

13. Short FAQ

Do I need a media query for every screen size?

No. Fluid sizing, wrapping, Flexbox, Grid, and normal flow often handle intermediate widths. Add a query when the content needs a distinct arrangement.

Should I design desktop first or mobile first?

Start with the narrow layout and progressively add columns or other enhancements when the content supports them. This keeps the baseline simple and makes cramped states visible early.

Is max-width: 100% enough for responsive images?

It prevents overflow, but it does not select a smaller or differently cropped source. Use srcset, sizes, and picture when source choice matters.

Why does my Grid item still overflow?

Grid tracks have automatic minimum sizes. Use minmax(0, 1fr) and constrain the overflowing child, especially for long URLs, tables, and code.

Can responsive CSS account for user preferences?

Yes. Media queries can respond to preferences such as reduced motion, as well as environmental conditions such as pointer and hover capability.

Responsive CSS checklist

  • Viewport meta tag is present.
  • Page and components use fluid widths and box-sizing: border-box.
  • Flexbox or Grid handles relationships before extra breakpoints are added.
  • Breakpoints are based on content failure and use relative units.
  • Images, video, SVG, canvas, iframes, tables, and long strings are checked for overflow.
  • srcset, sizes, or picture is used when one image source is not appropriate everywhere.
  • Keyboard use, zoom, readable text measure, and reduced motion are tested.
  • Narrow, intermediate, and wide widths are inspected with real content.

Responsive CSS is a set of layout decisions that cooperate: a flexible baseline, content-aware changes, suitable media sources, and preferences that respect the person using the page. Build those layers in that order and your page can adapt without a separate stylesheet for every device.