# Enlarged text cut off by fixed-height boxes

Source: https://easeweb.dev/learn/resize-text
Topics: Zoom, reflow and text spacing > Pages work at 200% zoom (WCAG 1.4.4)
The fix: Let every box that holds text grow with it. Either replace the fixed height with a minimum height and drop overflow: hidden, or do that and also fold the links behind a menu button when they no longer fit on one row. Both pass; the first is two lines of CSS, the second keeps a sticky header short at the cost of a script.
Test with: zoom, keyboard
References: https://www.w3.org/WAI/WCAG22/Understanding/resize-text.html https://www.w3.org/WAI/WCAG22/Techniques/failures/F69 https://www.w3.org/WAI/WCAG22/Techniques/css/C28 https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/examples/disclosure-navigation/ https://developer.mozilla.org/en-US/docs/Web/CSS/min-height https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver

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 stationery shop's header holds the logo, six links (Pens, Paper, Gift sets, Workshops, Journal, Contact) and the cart. At the default text size they sit on one row. Someone who reads with larger text sets the browser's font size to 200%. The links no longer fit on one row, so they wrap onto three. The header is still 80 pixels tall and centres its contents, so only the middle row shows: the rows above and below it are cut off. Pressing Tab still moves focus to those links, but the focused link can't be seen, and nothing on the screen says where it went.

## Why it fails

The header has `height: 80px`, chosen to match the design, and `overflow: hidden`, added to crop a background pattern. Enlarged text needs more height, but the box can't grow, so everything past 80 pixels is cut off and there is no way to scroll to it. Success criterion 1.4.4 asks that text can be resized to 200% without losing content or function. Here both are lost: the links can't be read, and the ones that can still be reached by keyboard can't be seen.

## Who is affected

People with low vision who enlarge text rather than zoom the whole page, so the layout stays wide enough to read. Older readers who set a larger default font size once and leave it. Keyboard users at that size, who Tab onto links that are off the visible edge of the header. On a phone, the same header can clip at the default size, because a narrow screen wraps the links too.

## What automation and AI miss

Most scanners load the page once, at the default size, where everything fits. axe has no rule for it. A probe has to enlarge the text, then compare each box's height with the text inside it. AI fixes often go wrong in one of three ways. Deleting `overflow: hidden` alone leaves the fixed height in place, so the extra rows spill out over the content below and overlap it. `overflow: auto` turns the header into a small scrolling box. `text-overflow: ellipsis` hides even more of the text.

## Before: a header with a fixed height

```html
<header class="site-header">
  <a href="/" class="logo">Inkwell &amp; Co</a>
  <nav aria-label="Main">
    <ul class="site-nav">
      <li><a href="/collections/pens">Pens</a></li>
      <li><a href="/collections/paper">Paper</a></li>
      <li><a href="/collections/gift-sets">Gift sets</a></li>
      <li><a href="/pages/workshops">Workshops</a></li>
      <li><a href="/blogs/journal">Journal</a></li>
      <li><a href="/pages/contact">Contact</a></li>
    </ul>
  </nav>
  <a href="/cart" class="cart">Cart (2)</a>
</header>
```

```css
.site-header {
  display: flex;
  align-items: center;
  gap: 24px;
  height: 80px;
  padding: 0 24px;
  overflow: hidden;
}
.site-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0 24px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.cart { margin-left: auto; }
```

At 200% the links need two or three rows, but the header stays 80 pixels tall and hides the rest. The links are still in the page and in the tab order, so focus moves onto text nobody can see.

## After: let the header grow

```css
.site-header {
  display: flex;
  align-items: center;
  gap: 24px;
  min-height: 80px; /* not height */
  padding: 0 24px;
  /* no overflow: hidden */
}
```

The header keeps its 80 pixels at the default size and grows when the text needs more. At 200% the links wrap onto more rows and the header grows to hold them, so every link is visible and every focused link can be seen. The HTML doesn't change. If the background pattern needs cropping, crop it on an element of its own (see Implementation decisions).

## After: fold the links into a menu when they don't fit

```html
<header class="site-header">
  <a href="/" class="logo">Inkwell &amp; Co</a>
  <button type="button" class="menu-button" aria-expanded="false" hidden>Menu</button>
  <nav aria-label="Main">
    <ul class="site-nav">
      <!-- the same six links -->
    </ul>
  </nav>
  <a href="/cart" class="cart">Cart (2)</a>
</header>
```

```css
.site-header {
  position: relative;
  display: flex;
  align-items: center;
  gap: 24px;
  min-height: 80px;
  padding: 0 24px;
}
/* Folded: the links open in a panel under the header. */
.is-folded nav {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  padding: 16px 24px;
  background: #fff;
}
.is-folded .site-nav { flex-direction: column; }
.is-folded .menu-button[aria-expanded="false"] + nav { display: none; }
```

```js
const header = document.querySelector('.site-header');
const logo = header.querySelector('.logo');
const button = header.querySelector('.menu-button');
const links = header.querySelectorAll('.site-nav li');

// Fold the links behind the button whenever they need more than one row.
function fit() {
  header.classList.remove('is-folded');
  button.hidden = true;
  const fold = links[links.length - 1].offsetTop > links[0].offsetTop;
  header.classList.toggle('is-folded', fold);
  button.hidden = !fold;
  if (!fold) button.setAttribute('aria-expanded', 'false');
}
// The header resizes with the window, and the logo with the text.
const observer = new ResizeObserver(fit);
observer.observe(header);
observer.observe(logo);

button.addEventListener('click', () => {
  const open = button.getAttribute('aria-expanded') !== 'true';
  button.setAttribute('aria-expanded', String(open));
});
```

The script measures the links laid out in full. When they need a second row, for any reason (enlarged text, zoom, a narrow window, a longer translation), it hides them behind a native button that says whether it is open. The header stays one row tall and the links are one press away. It still uses `min-height`, so the logo and the button can grow too.

## Which option to use

Both options keep every link readable and reachable at 200%, and both meet 1.4.4. Letting the header grow is two lines of CSS with nothing to maintain, so start there. Its cost shows on a sticky or fixed header: two or three rows of large text can take a third of a laptop screen and stay there while people scroll. Folding the links keeps the header short at any size, which suits a sticky header or a design that must stay on one row, but it adds a script and a menu that has to be kept working. If your theme already has a menu button for small screens, folding is mostly a matter of triggering it by fit rather than by screen width.

## Implementation decisions

**Find the other fixed heights.** The header is rarely the only one. Look for `height`, `max-height` and `line-height` in pixels on anything that holds text: announcement bars, buttons, badges, card titles, tab rows. `line-height: 80px`, used to centre a link vertically, makes every wrapped row 80 pixels tall; centre with `align-items` and padding instead.

**Units alone don't fix it.** A height in `rem` or `em` (technique C28) grows with the text, but only in proportion. When zoom narrows the page, or a translation is longer, the links wrap onto more rows than the box was sized for, and the extra rows are cut off again. Relative units help only while the number of lines stays the same.

**Clip decoration, not text.** If something must be cropped, crop it on an element that holds no text: a pseudo-element or a wrapper for the background. `overflow-x: clip` crops sideways only and still lets the box grow; `overflow-x: hidden` does not, because it turns the other axis into a scroll box.

**Offsets that assume the old height.** A `position: fixed` header usually comes with `body { padding-top: 80px }`, and sometimes `scroll-padding-top: 80px` or a sub-menu at `top: 80px`. Once the header can grow, those hide content under it. Prefer `position: sticky`, which keeps its place in the flow, or set the offset from the header's measured height.

**Folding and focus.** If the links fold while one of them has focus, close the menu and move focus to the menu button, or the focus is lost. Close the menu on Escape and return focus to the button. A theme's existing small-screen drawer usually does this already.

**Line clamps.** A clamped excerpt whose full text is one click away is a design choice; a clamped price, label or menu item is lost content.

**Don't block zoom.** A viewport tag with `user-scalable=no`, or a `maximum-scale` below 2, stops people pinch-zooming on a phone, which fails 1.4.4 on its own. Use `<meta name="viewport" content="width=device-width, initial-scale=1">` and nothing more.

## Verify the fix

1. Zoom the browser to 200% and check that every word in the header and the page can be read, with nothing cut off or overlapping.
2. Reset zoom and set the text size alone to 200% (Firefox's Zoom text only, or the browser's font size setting), and check again.
3. At 200%, Tab through the header and check that every focused link is visible; if the links fold, open the menu with Enter and Space and reach every link.
4. Try the longest real content: translations, long product names, an extra menu item.
5. On a sticky or fixed header, scroll at 200% and check that enough of the screen is left to read and nothing is hidden underneath.

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the shared header or theme settings change.

## Limits

This is a starting pattern, not a guarantee for every application. Strictly, 1.4.4 can be met by full-page zoom alone, but many people enlarge text only, and a box that clips then also clips at zoom when the page is narrow. Paths and class names in the snippets are placeholders. Test every page template, not only the header.
