# Pages that scroll sideways on a narrow screen

Source: https://easeweb.dev/learn/reflow
Topics: Zoom, reflow and text spacing > Content fits a 320px screen without sideways scrolling (WCAG 1.4.10)
The fix: Let every column shrink to the width of its container. Either cap the grid's minimum column width with min(100%, 320px), or make the grid one column by default and add columns only from a breakpoint. Both pass on this page; the first holds wherever the grid is placed, the second only while the breakpoint matches the space the grid really gets.
Test with: zoom, keyboard
References: https://www.w3.org/WAI/WCAG22/Understanding/reflow.html https://www.w3.org/WAI/WCAG22/Techniques/css/C32 https://www.w3.org/WAI/WCAG22/Techniques/css/C38 https://www.w3.org/WAI/WCAG22/Techniques/failures/F102 https://developer.mozilla.org/en-US/docs/Web/CSS/min https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-wrap

This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.

## What happens

A pottery studio lists its classes as cards: Wheel basics, Glazing, Hand building. Each card has a title, a date, a price and a Book button. On a laptop the cards sit three to a row; on the phones the team tests on they stack in one column. Someone with low vision zooms the page to 400% in a window 1280 pixels wide, which gives the page 320 CSS pixels to work with. The cards no longer fit. Every card runs 20 pixels off the right edge, the ends of its longer lines are cut off, and the page scrolls sideways. To read each card they scroll right, then back left, and again for the next card.

## Why it fails

The grid uses `repeat(auto-fill, minmax(320px, 1fr))`, which looks responsive: it drops to fewer columns as the screen narrows. But it never makes a column narrower than 320 pixels, a width chosen so that one card exactly fills a 360-pixel phone, the narrowest the team tests on. The page has 20 pixels of padding on each side, so a 320-pixel screen leaves 280 pixels for the grid, and the one remaining column sticks out of it by 40 pixels, 20 of them past the edge of the screen. Phone testing never shows it: few phones in use today are narrower than 360 pixels. Zoom is what gets the page down to 320. Success criterion 1.4.10 asks that content can be read at a width of 320 CSS pixels without scrolling in two directions, except for content such as data tables, maps and diagrams that needs a two-dimensional layout. A list of cards is not that kind of content.

## Who is affected

Anyone who sees the page at 320 CSS pixels wide. Today that is mostly people who zoom a desktop browser to 400% or use a magnifier; on a phone it takes zoom or a larger display size setting, since few phones are that narrow on their own. A wide window does not help at that zoom, because zoom shrinks the width in CSS pixels.

## What automation and AI miss

Most scanners load the page once, at desktop width, where three columns fit with room to spare. axe has no rule for reflow. A probe has to set the viewport to 320 pixels and compare the page's scroll width with its visible width, then find which element is too wide. AI fixes often go wrong in one of three ways:

- `overflow-x: hidden` on the body: the scrollbar goes, but the right edge of every card, and the end of every long line in it, is now cut off with no way to reach it.
- A breakpoint at 360 or 390 pixels, the widths of popular phones: it misses 320, the width zoom produces and the criterion names.
- Shrinking the text or the padding until it fits: it moves the edge a little and breaks again with the next long title.

## Before: a grid with a fixed minimum column width

```html
<main class="page">
  <h1>Classes this month</h1>
  <ul class="classes">
    <li class="class-card">
      <h2>Wheel basics</h2>
      <p>Saturday 14 November, 10:00 to 13:00</p>
      <p class="price">$65</p>
      <a href="/classes/wheel-basics" class="book">Book</a>
    </li>
    <!-- Glazing, Hand building -->
  </ul>
</main>
```

```css
.page { padding: 0 20px; }
.classes {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: 16px;
  margin: 0;
  padding: 0;
  list-style: none;
}
```

At 320 pixels the grid has 280 pixels, but each column insists on 320, so every card runs 20 pixels past the right edge of the screen and the page scrolls sideways.

## After: cap the column minimum at the container width

```css
.page { padding: 0 20px; }
.classes {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 320px), 1fr));
  gap: 16px;
  margin: 0;
  padding: 0;
  list-style: none;
}
```

`min(100%, 320px)` is 320 pixels while the grid is wider than that, and the grid's own width when it is narrower. On a wide screen nothing changes: columns are still at least 320 pixels and fill the row. At 320 pixels the single column shrinks to 280 and fits. Because it measures the grid itself, not the window, it holds wherever the grid is placed: beside a sidebar, inside a dialog, or in a narrow column.

## After: one column first, more from a breakpoint

```css
.page { padding: 0 20px; }
.classes {
  display: grid;
  grid-template-columns: 1fr;
  gap: 16px;
  margin: 0;
  padding: 0;
  list-style: none;
}
@media (min-width: 40em) {
  .classes {
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  }
}
```

Below 40em (640 pixels at the default text size) the grid is a single column as wide as the screen; above it, the grid lays out columns as before. The breakpoint is in `em`, so it moves with the browser's text size. This passes on this page because at 640 pixels the grid gets 600, far more than one 320-pixel column needs. It depends on that staying true: the media query measures the window, not the grid, so if the same grid is placed beside a 360-pixel sidebar, or in a dialog, it gets less than 320 pixels above the breakpoint and overflows again. A container query (`@container (min-width: 40em)`) on the grid's parent removes that condition.

## Which option to use

Both options make the class list fit at 320 pixels and pass 1.4.10 on this page. Capping the minimum with `min()` is recommended: it is one change to one line, it keeps the wide layout exactly as designed, and it measures the space the grid really has, so it can't be broken by a change elsewhere in the layout. The breakpoint option is the more familiar pattern and is fine where you already have a mobile-first stylesheet, but it is only as good as its breakpoint, which has to be checked again each time the grid moves into a different layout.

## Implementation decisions

**Find the other fixed widths.** The grid is rarely the only one. Look for `width` and `min-width` in pixels on containers, sidebars, forms, buttons and cards, and for `100vw`, which includes the vertical scrollbar and is a few pixels wider than the page. Prefer `max-width` with `width: 100%`, or `min(100%, …)`.

**Flex and grid children.** A flex child's minimum width defaults to the width of its content, so one long word or wide table inside it pushes the whole layout wide. Add `min-width: 0` to the child and let the content wrap.

**Long words.** Email addresses, URLs, order numbers and German compound words don't wrap by default. `overflow-wrap: anywhere` on text containers lets them break when nothing else fits, without changing ordinary text.

**Media.** Images, video and iframes keep their width attribute unless told otherwise; give them `max-width: 100%` and `height: auto`. An embedded map or chart that needs two dimensions can scroll inside its own box.

**Content that may scroll.** The exception in 1.4.10 covers data tables, maps, diagrams, video, games, toolbars that must stay on one line, and code where line breaks change meaning. Wrap a wide table in an element with `overflow-x: auto`, a visible edge, `tabindex="0"`, and a role and name (`role="region"` with `aria-labelledby` pointing at the caption), so keyboard users can scroll it. The rest of the page must still reflow.

**Don't hide the overflow.** `overflow-x: hidden` or `clip` on the body or a wrapper removes the scrollbar but not the problem; the cut-off part becomes unreachable, which also fails 1.4.10 (failure F102 covers content that disappears).

**The viewport tag.** Without `<meta name="viewport" content="width=device-width, initial-scale=1">` a phone lays the page out at about 980 pixels and shrinks it. A fixed `width=1024` in that tag has the same effect, and stops the page from reflowing on any phone.

**Vertical text and short screens.** For content that scrolls sideways by design, such as a horizontal timeline, the criterion asks the same at a height of 256 pixels. Sticky headers and footers at 400% can cover most of a short screen; see the focus-not-obscured guide.

## Verify the fix

1. Set the viewport to 320 by 256 CSS pixels (responsive mode in the browser's developer tools) and scroll each page template from top to bottom: nothing but tables, maps, diagrams, code and media scrolls sideways, and nothing is cut off.
2. In a window 1280 pixels wide, zoom the browser to 400% and repeat; check that the menu, forms and dialogs reflow too, and that every control can still be reached with Tab.
3. Try the longest real content: translations, long class names, email addresses, an extra card.
4. Place the component in every layout it is used in (full width, beside a sidebar, inside a dialog) and check it again at 320 pixels.
5. For any content that keeps two dimensions, check that it scrolls inside its own box, and that the box can be reached with Tab and scrolled with the arrow keys.

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the shared layout or grid styles change.

## Limits

This is a starting pattern, not a guarantee for every application. Passing at 320 pixels doesn't make a layout good at 320 pixels: check that the reading order still makes sense once the columns stack, and that nothing important has been pushed far down the page. Paths and class names in the snippets are placeholders. Test every page template, not only the one with the card grid.
