CSS overflow: what happens to content that does not fit

overflow decides what a box does with content bigger than itself: show it, cut it, or scroll it. Five values, one pairing rule, and one condition that trips up almost every first attempt.

CSS overflow controls what a box does with content that is bigger than the box. visible (the default) lets it spill out, hidden and clip cut it off, and scroll and auto make the box scroll.

None of them does anything until the box has a size its content can exceed.

Try the five values on one box. Then tick height: auto and watch every value stop mattering for the vertical direction.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CSS overflow playground</title>
<style>
  body { margin: 0; padding: 16px; font-family: system-ui, sans-serif; color: #1d2330; background: #f4f5f7; }
  .controls { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
  .controls label { font: 13px ui-monospace, Consolas, monospace; background: #fff; border: 1px solid #d5d9e0; border-radius: 8px; padding: 6px 9px; cursor: pointer; }
  .controls input:checked + span { font-weight: 700; color: #0f5132; }
  .sliders { display: grid; grid-template-columns: auto 1fr auto; gap: 6px 10px; align-items: center; font-size: 13px; margin-bottom: 14px; max-width: 420px; }
  .stage { min-height: 250px; }

  /* the box under test */
  #box {
    overflow: visible;         /* changed by the buttons */
    width: 220px;
    height: 110px;
    padding: 10px;
    background: #fff;
    border: 2px solid #2563eb;
    border-radius: 8px;
    font-size: 14px; line-height: 1.45;
  }
  .url { font-family: ui-monospace, Consolas, monospace; color: #9a3412; }
  #out { white-space: pre-wrap; font: 13px ui-monospace, Consolas, monospace; background: #1d2330; color: #e5e7eb; border-radius: 8px; padding: 8px 10px; }
</style>
</head>
<body>
<div class="controls" id="values">
  <label><input type="radio" name="v" value="visible" checked> <span>visible</span></label>
  <label><input type="radio" name="v" value="hidden"> <span>hidden</span></label>
  <label><input type="radio" name="v" value="clip"> <span>clip</span></label>
  <label><input type="radio" name="v" value="scroll"> <span>scroll</span></label>
  <label><input type="radio" name="v" value="auto"> <span>auto</span></label>
</div>
<div class="sliders">
  <span>width</span><input type="range" id="w" min="120" max="340" value="220"><span id="wv">220px</span>
  <span>height</span><input type="range" id="h" min="60" max="300" value="110"><span id="hv">110px</span>
  <span></span><label><input type="checkbox" id="auto"> height: auto</label><span></span>
</div>
<div class="stage">
  <div id="box">
    This box has more text than it can hold. Pick a value above and watch what happens to the lines that do not fit.
    The address below has no spaces, so it cannot wrap:
    <span class="url">example.com/a/very/long/path/without/any/spaces</span>
  </div>
</div>
<div id="out"></div>

<script>
  const box = document.getElementById('box');
  const out = document.getElementById('out');
  const w = document.getElementById('w'), h = document.getElementById('h'), auto = document.getElementById('auto');

  function update() {
    box.style.overflow = document.querySelector('input[name=v]:checked').value;
    box.style.width = w.value + 'px';
    box.style.height = auto.checked ? 'auto' : h.value + 'px';
    h.disabled = auto.checked;
    document.getElementById('wv').textContent = w.value + 'px';
    document.getElementById('hv').textContent = auto.checked ? 'auto' : h.value + 'px';
    // scrollHeight = height of the content, clientHeight = height of the visible area
    const tooTall = box.scrollHeight > box.clientHeight;
    const tooWide = box.scrollWidth > box.clientWidth;
    out.textContent = 'overflow: ' + box.style.overflow +
      '\ncontent overflows: ' + (tooTall ? 'down ' : '') + (tooWide ? 'sideways' : '') + (!tooTall && !tooWide ? 'no' : '');
  }
  document.getElementById('values').addEventListener('change', update);
  [w, h, auto].forEach(el => el.addEventListener('input', update));
  update();
</script>
</body>
</html>
Five values, one box. The width and height sliders change how much of the content fits.

The five values

The same box and content under each overflow value.
The same box and content under each overflow value.
Value Content that does not fit Scrollable Typical use
visible Drawn outside the box No The default. Most boxes
hidden Cut off at the padding edge By script only, no bar Rounded cards, image crops
clip Cut off at the padding edge No, not even by script Cutting without side effects
scroll Reached by scrolling Yes, bars reserved Panels whose width must not shift
auto Reached by scrolling Yes, bars when needed Chat logs, code blocks, side panels

auto is the right choice for almost every scrolling box. scroll only differs on systems that draw classic scrollbars, where it keeps room for a bar even when nothing overflows.

On phones, and on systems with overlay scrollbars, the bars appear only while scrolling, so the two look the same.

To change how the bar itself looks, see styling the scrollbar.

overflow needs a size to do anything

A block box with no height grows to fit its content. Content that always fits never overflows, so overflow: auto on such a box shows no scrollbar and overflow: hidden cuts nothing.

This is the most common reason overflow "does nothing". The fix is a limit:

.panel {
  max-height: 300px;   /* grows up to 300px, then scrolls */
  overflow-y: auto;
}

max-height is usually better than height: a short list stays short, and a long one scrolls. In a flex or grid layout the size can also come from the layout itself, as long as the box is not allowed to grow past it.

Sideways is different. A block's width is already limited by its parent, so horizontal overflow happens when something inside cannot shrink or wrap: a long word or address, a wide image, or white-space: nowrap.

Make a box scroll

  1. Give the box a size. Set a height or max-height, and a width if it should scroll sideways.
  2. Set overflow. overflow: auto for both directions, or overflow-y: auto for just one.
  3. Stop scroll chaining. Add overscroll-behavior: contain so the page does not take over when the box reaches its end. More on that below.

Wide tables are a special case, because a table ignores overflow on itself. Making an HTML table scroll shows the wrapper that fixes it.

overflow-x and overflow-y do not mix freely

overflow is shorthand for overflow-x and overflow-y, and you can set them apart. There is one catch. If one axis is hidden, scroll or auto, the other axis cannot stay visible. The browser changes it to auto.

What you write and what the browser actually uses.
What you write and what the browser actually uses.

So overflow-x: hidden on its own often gives the box a vertical scrollbar you did not ask for. The content that should spill down is now trapped inside the box.

clip follows a similar rule: next to hidden, scroll or auto it becomes hidden. But clip next to visible is allowed and kept. That makes overflow-x: clip the way to cut the sides while letting content spill downward.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>overflow-x, overflow-y and clip</title>
<style>
  body { margin: 0; padding: 16px; font-family: system-ui, sans-serif; color: #1d2330; background: #f4f5f7; }
  h2 { font-size: 15px; margin: 0 0 8px; }
  section { background: #fff; border: 1px solid #e1e4ea; border-radius: 12px; padding: 12px 14px; margin-bottom: 14px; }
  .row { display: flex; flex-wrap: wrap; gap: 8px 14px; font: 13px ui-monospace, Consolas, monospace; margin-bottom: 8px; }
  select, button { font: inherit; }
  .result { font: 13px ui-monospace, Consolas, monospace; background: #1d2330; color: #e5e7eb; border-radius: 8px; padding: 8px 10px; white-space: pre-wrap; }
  .result b { color: #fbbf24; }

  /* part 1: a box whose tall content should spill down */
  #pair { width: 200px; height: 60px; border: 2px solid #2563eb; border-radius: 6px; padding: 6px; font-size: 13px; margin: 8px 0 50px; }

  /* part 2: the page-like scroller, and a card with a sticky header inside */
  .scroller { height: 190px; overflow-y: auto; border: 2px solid #475569; border-radius: 8px; background: #f8fafc; }
  .card { margin: 10px; border-radius: 8px; background: #fff; border: 1px solid #d5d9e0; }
  .card.hidden { overflow: hidden; }
  .card.clip { overflow: clip; }
  .card h3 { position: sticky; top: 0; margin: 0; padding: 8px 10px; font-size: 14px; background: #0f5132; color: #fff; }
  .card li { padding: 6px 0; font-size: 13px; }
</style>
</head>
<body>
<section>
  <h2>1. Set one axis, the other one changes</h2>
  <div class="row">
    <label>overflow-x: <select id="ox"><option>visible</option><option selected>hidden</option><option>clip</option><option>scroll</option><option>auto</option></select></label>
    <label>overflow-y: <select id="oy"><option selected>visible</option><option>hidden</option><option>clip</option><option>scroll</option><option>auto</option></select></label>
  </div>
  <div id="pair">Line 1 of the tall content<br>Line 2<br>Line 3<br>Line 4<br>Line 5</div>
  <div class="result" id="pairOut"></div>
</section>

<section>
  <h2>2. A sticky header inside a card with overflow</h2>
  <div class="row">
    <label><input type="radio" name="c" value="hidden" checked> card: overflow: hidden</label>
    <label><input type="radio" name="c" value="clip"> card: overflow: clip</label>
  </div>
  <div class="scroller" id="scroller">
    <div class="card hidden" id="card">
      <h3>Sticky header (top: 0)</h3>
      <ol><li>Scroll this grey box.</li><li>Row 2</li><li>Row 3</li><li>Row 4</li><li>Row 5</li><li>Row 6</li><li>Row 7</li><li>Row 8</li><li>Row 9</li><li>Row 10</li><li>Row 11</li><li>Row 12</li></ol>
    </div>
  </div>
  <div class="result" id="stickyOut"></div>
</section>

<script>
  const pair = document.getElementById('pair');
  const ox = document.getElementById('ox'), oy = document.getElementById('oy');
  function showPair() {
    pair.style.overflowX = ox.value;
    pair.style.overflowY = oy.value;
    const s = getComputedStyle(pair);  // what the browser actually uses
    const note = s.overflowY !== oy.value || s.overflowX !== ox.value ? '  <b>changed by the browser</b>' : '';
    document.getElementById('pairOut').innerHTML =
      'you wrote:       x ' + ox.value + ', y ' + oy.value +
      '\nbrowser uses:    x ' + s.overflowX + ', y ' + s.overflowY + note;
  }
  ox.addEventListener('change', showPair);
  oy.addEventListener('change', showPair);
  showPair();

  const card = document.getElementById('card'), scroller = document.getElementById('scroller');
  const header = card.querySelector('h3'), stickyOut = document.getElementById('stickyOut');
  function showSticky() {
    // is the header still at the top edge of the grey box?
    const gap = Math.round(header.getBoundingClientRect().top - scroller.getBoundingClientRect().top);
    stickyOut.textContent = 'scrolled ' + Math.round(scroller.scrollTop) + 'px, header is ' +
      (gap >= 0 && gap <= 3 ? 'stuck at the top' : gap < 0 ? 'scrolled out of view' : gap + 'px from the top');
  }
  document.querySelectorAll('input[name=c]').forEach(r => r.addEventListener('change', () => {
    card.className = 'card ' + r.value;
    showSticky();
  }));
  scroller.addEventListener('scroll', showSticky);
  showSticky();
</script>
</body>
</html>
Top: the computed values the browser uses. Bottom: the same sticky header under a card with hidden, then clip.

hidden vs clip

Both values cut off what does not fit, and they look the same. The difference is that hidden still makes the box a scroll container. It has no scrollbar, but script can scroll it with scrollTop or scrollIntoView(), and it changes how its children behave.

The child most affected is position: sticky. A sticky element sticks inside its nearest ancestor that is a scroll container.

Put overflow: hidden on a card and the card becomes that ancestor. The card never scrolls, so the header has nothing to stick to and scrolls away with the page.

hidden turns the card into the sticky header's scroll box. clip does not.
hidden turns the card into the sticky header's scroll box. clip does not.

overflow: clip cuts the same edges without creating a scroll container, so the header keeps sticking to the real scroller. CSS position covers sticky's other requirements.

overflow contains floats

Any overflow value other than visible and clip starts a new block formatting context. In plain terms, the box becomes a self-contained layout island. Two things follow:

  • Floats are contained. A parent with only floated children normally collapses to zero height. With overflow: hidden or auto, it grows to wrap them.
  • Margins stop leaking. A child's top margin no longer collapses through the parent's top edge.

That is why overflow: hidden became a classic float fix. It also clips, which is a side effect you may not want. display: flow-root creates the same layout island without cutting anything. CSS float shows both.

overflow: clip does not start a block formatting context, so it will not contain floats.

Finding what scrolls a phone page sideways

When a page scrolls sideways on a phone, one element is wider than the screen. Hiding the overflow on body covers it up, but the element is still too wide and its right side is cut off. Find it and fix it instead.

Open the page in your desktop browser's developer tools, switch to a phone-sized view, and paste this into the Console:

const vw = document.documentElement.clientWidth;
document.querySelectorAll('body *').forEach(el => {
  const r = el.getBoundingClientRect();
  if (r.right > vw + 1 || r.left < -1) {
    console.log(el, Math.round(r.right));
    el.style.outline = '2px solid red';
  }
});

Every element sticking out gets a red outline. The outermost one is usually the cause, and its children are just along for the ride.

Elements inside a box that scrolls on purpose, such as a wide table in a scroll wrapper, are listed too and can be ignored.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Find what overflows</title>
<style>
  * { box-sizing: border-box; }
  body { margin: 0; font-family: system-ui, sans-serif; color: #1d2330; background: #f4f5f7; }
  .tools { padding: 12px 14px; background: #1d2330; color: #e5e7eb; }
  .tools button { font: 600 13px system-ui, sans-serif; padding: 7px 11px; border-radius: 8px; border: 0; margin: 0 6px 6px 0; cursor: pointer; }
  #find { background: #fbbf24; color: #1d2330; }
  #fix { background: #d6f2df; color: #0f5132; }
  #report { font: 12.5px/1.5 ui-monospace, Consolas, monospace; white-space: pre-wrap; margin: 4px 0 0; }

  /* a small sample page */
  header { padding: 14px 16px; background: #fff; border-bottom: 1px solid #e1e4ea; font-weight: 700; }
  main { padding: 14px 16px; }
  .card { background: #fff; border: 1px solid #e1e4ea; border-radius: 10px; padding: 12px; margin-bottom: 12px; font-size: 14px; line-height: 1.5; }

  /* three sneaky causes of sideways scrolling */
  .promo { width: 100vw; margin-left: 16px; padding: 12px; border-radius: 10px; background: #2563eb; color: #fff; }
  .link { font-family: ui-monospace, Consolas, monospace; color: #9a3412; }
  .chart { display: block; }

  /* the fixes */
  .fixed .promo { width: auto; margin-left: 0; }
  .fixed .link { overflow-wrap: anywhere; }
  .fixed .chart { max-width: 100%; height: auto; }

  .flag { outline: 3px solid #dc2626; outline-offset: -3px; }
</style>
</head>
<body>
<div class="tools">
  <button id="find">Find wide elements</button><button id="fix">Apply fixes</button>
  <div id="report">This page scrolls sideways. Press the yellow button.</div>
</div>
<header>Sample page</header>
<main>
  <div class="promo">Promo banner, width: 100vw</div>
  <p class="card">Your tracking code: <span class="link">TRK8F14E45FCEEA167A5A36DEDD4BEA2543C9F0AE1B2D7E4F8A0C3B9E1D5F7A2C4E6B8D0F1A3C5E7B9D2F4A6C8E0B</span></p>
  <div class="card">
    <svg class="chart" width="520" height="90" viewBox="0 0 520 90"><rect width="520" height="90" rx="8" fill="#e0e7ff"/><path d="M10 80 L120 50 L220 60 L330 25 L510 15" stroke="#4f46e5" stroke-width="4" fill="none"/></svg>
  </div>
</main>

<script>
  // List every element that sticks out past the right or left edge of the viewport.
  function findWide() {
    const vw = document.documentElement.clientWidth;
    const wide = [];
    document.querySelectorAll('body *').forEach(el => {
      el.classList.remove('flag');
      const r = el.getBoundingClientRect();
      if (r.right > vw + 1 || r.left < -1) wide.push(el);
    });
    // keep only the outermost ones: the element that causes it, not its children
    const causes = wide.filter(el => !wide.includes(el.parentElement));
    causes.forEach(el => el.classList.add('flag'));
    const pageWidth = document.documentElement.scrollWidth;
    document.getElementById('report').textContent =
      'viewport ' + vw + 'px, page ' + pageWidth + 'px\n' +
      (causes.length
        ? causes.map(el => el.tagName.toLowerCase() + '.' + el.classList[0] +
            '  right edge ' + Math.round(el.getBoundingClientRect().right) + 'px').join('\n')
        : 'nothing sticks out');
  }
  document.getElementById('find').addEventListener('click', findWide);
  document.getElementById('fix').addEventListener('click', () => {
    document.body.classList.toggle('fixed');
    document.getElementById('fix').textContent = document.body.classList.contains('fixed') ? 'Undo fixes' : 'Apply fixes';
    findWide();
  });
</script>
</body>
</html>
A sample page with three hidden culprits. Find them, then apply the fixes and check again.

The usual suspects and their fixes:

  • width: 100vw plus a margin or padding. 100vw is the full viewport, and on systems with classic scrollbars it includes the scrollbar too. Use width: auto or 100%.
  • Images, videos and embeds with a fixed width. Add max-width: 100%; height: auto;. See CSS max-width.
  • Long words, links and codes. Add overflow-wrap: anywhere, covered in HTML word break. For one line with dots at the end, use text-overflow: ellipsis.

A missing viewport meta tag also makes a page look wrong on phones, but that shrinks the whole page rather than scrolling it sideways.

Nested scrolling: overscroll-behavior

Scroll to the end of a scrolling panel and keep going. The browser passes the leftover scroll to the next scroller up, usually the page.

This is called scroll chaining, and in a chat box or a side menu it feels like the page ran away.

.chat {
  max-height: 60vh;
  overflow-y: auto;
  overscroll-behavior: contain;  /* stop at the end, do not scroll the page */
}

contain stops the chaining and keeps the box's own bounce or glow effect. none stops both. The property only matters on a box that actually scrolls.

When it does not work

What you see Cause Fix
overflow: auto shows no scrollbar The box has no height and grows with its content Add height or max-height
An unwanted vertical scrollbar appears overflow-x: hidden turned overflow-y into auto Use overflow-x: clip
A dropdown or tooltip is cut off An ancestor with overflow: hidden clips it Move the overflow to an inner box, or see below
Shadows or focus rings are cut at the edge The overflow box clips everything past its padding edge Add padding to the overflow box
position: sticky stopped sticking An ancestor has overflow: hidden or auto Use overflow: clip on that ancestor
The page scrolls sideways on phones One element is wider than the screen Find it with the snippet above
A scroll box inside a flex or grid item widens the page The item's minimum width defaults to its content width min-width: 0 on the flex or grid item
Scrolling a panel also scrolls the page Scroll chaining overscroll-behavior: contain

An absolutely positioned dropdown is clipped when its positioned ancestor (the position: relative one) is the overflow box or sits inside it.

To fix it, put position: relative on an element outside the clipped box, or show the menu with the popover attribute, which draws it above the page.

Overflow problems depend on screen width, so a screenshot from your desktop rarely shows what a phone user sees. A link that opens the real page lets the other person resize, scroll and see the bug, or the fix, for themselves.

Paste the page into a NOS document and choose Create share link. HTML to link walks through it.

The page renders as written and its scripts run, so the overflow finder above works for the people you send it to. If you change the code later, the same link shows the new version.

Questions people ask

What is the difference between overflow auto and overflow scroll?

Both make the box scrollable. auto shows a scrollbar only when the content is bigger than the box. scroll keeps room for scrollbars even when everything fits. On phones and on systems with overlay scrollbars the bars only appear while scrolling, so the two look the same there.

Why is overflow: hidden not working?

Usually the box has no fixed height, so it grows to fit its content and nothing ever overflows. Give it a height or max-height. If content still escapes, it is often absolutely positioned against an element outside the box, which overflow does not clip.

What is the difference between overflow hidden and overflow clip?

Both cut off what does not fit. hidden still makes the box a scroll container: script can scroll it and sticky children stick to it. clip makes no scroll container at all, so it cannot be scrolled and a sticky child sticks to the real scroller further up. clip also does not contain floats.

Why does overflow-x: hidden add a vertical scrollbar?

When one axis is hidden, scroll or auto, the other axis cannot stay visible. The browser turns overflow-y: visible into auto. If you only want to cut the sides and let the content spill down, use overflow-x: clip, which can pair with visible.

How do I stop my page scrolling sideways on a phone?

Find the element that is wider than the screen and fix that element. Common causes are width: 100vw with a margin, an image or embed with a fixed width, and long words or links that cannot wrap. Hiding the overflow on body only hides the symptom.

Keep reading