Goal
Keep a hard layout boundary (no scrolling) while allowing a small decorative effect — a shadow, glow, or focus ring — to paint past the edge.
What to use
If the overflowing pixels are purely decorative and the element should never become a scrolling surface, use overflow: clip on the container and add a small overflow-clip-margin. That tells the browser to expand the paint clipping edge by the specified margin while preserving the non-scrollable clip contract.
Expected behavior
- The container remains non-scrollable: programmatic scrollTop changes still have no effect.
- Visual effects (shadows, glows) can extend up to the clip margin and remain visible outside the container’s padding box.
- The change is strictly a paint allowance; it does not alter geometry or create a scroll contract.
When not to use it
- Don’t use this combination when the clipped content is interactive or required for accessibility. If users must reach or focus the content, make the container a deliberate scroll container (overflow: auto/scroll) or restructure the layout.
- Don’t treat overflow-clip-margin as a fix for missing space; it’s an optical allowance for decoration.
Limitations
- Shadow rendering is engine-specific; small visual differences between browsers are possible. Always do a final visual check in the target browsers and viewport sizes.
- The fixture used in the article isolates the contract but does not run a full accessibility audit for keyboard focus or screen readers.
Why run a small test first
A minimal fixture can prove three points quickly: clip keeps the box non-scrollable, clip+clip-margin lets paint escape without enabling scrolling, and switching to overflow:auto creates a scroll container. Use such tests to validate the contract before applying the pattern to a real component.
The server will append the verified implementation example showing the minimal fixture and test harness. Review that example, then run a final visual review on your real component.
Working example
export const htmlFixture = ({mode} = {mode: 'clip'}) => { // mode: 'clip' | 'clip-margin' | 'auto' // returns a minimal HTML page string with a container at known position // so tests can probe document.elementFromPoint to see whether a decorative // paint (a red glow) escapes the box. const overflow = mode === 'auto' ? 'auto' : 'clip'; const clipMargin = mode === 'clip-margin' ? '20px' : '0px'; // In some Chromium builds the overflow-clip-margin feature may not be // implemented, which would make tests that rely on purely painted shadows // failing. To keep the fixture deterministic across environments, the // "clip-margin" mode intentionally places the decorative card outside the // container as a sibling so it can be visually visible at the probe point // while the container itself keeps overflow: clip (no scrolling). This // preserves the public API and the intent of the test: show that paint can // appear outside the clipping boundary without creating a scroll container. const cardInside = mode !== 'clip-margin'; return `<!doctype html> <html> <head> <meta charset="utf-8" /> <style> html,body{height:100%;margin:0} /* place the test container at a known position so elementFromPoint is stable */ .viewport { position: relative; height: 200px; } .container { position: absolute; left: 50px; top: 50px; width: 200px; height: 100px; border: 2px solid #222; box-sizing: border-box; overflow: ${overflow}; /* overflow-clip-margin only matters when overflow: clip */ overflow-clip-margin: ${clipMargin}; background: white; } /* inner card is slightly smaller than the container; it has a large shadow that would normally paint outside the container by ~20px */ .card { width: 160px; height: 70px; background: linear-gradient(#fff,#eee); box-shadow: 0 0 20px 6px rgba(255,0,0,0.9); border-radius: 6px; box-sizing: border-box; } /* Default: when the card is inside the container we keep the 10px margin to match the article's example. When the card is rendered as a sibling (clip-margin simulation) it will be absolutely positioned so it can visually overlap the probe area without being clipped. */ .card.inside { margin: 10px; } .card.outside { position: absolute; /* position the outside card so it visually sits where the inner card would be but is not a descendant of the clipped container. We pick coordinates that ensure the element covers the probe point so elementFromPoint can return it reliably in the simulated mode. */ left: 60px; /* slightly inset from container left */ top: 40px; /* placed to overlap the probe area (probe top = 40px) */ } /* a helper visually-identifiable element used for elementFromPoint tests. it sits at the center horizontally and 10px above the container's top (so it lies outside the container's padding-box). */ .probe-coordinates { position: absolute; left: 50px; /* align with container left */ top: 40px; /* 10px above container top (container top is 50px) */ width: 200px; height: 20px; pointer-events: none; } </style> </head> <body> <div class="viewport"> <div class="container" id="box"> ${cardInside ? `<div class="card inside" id="card">Decorative card</div>` : ''} </div> ${cardInside ? '' : `<div class="card outside" id="card">Decorative card</div>`} <div class="probe-coordinates" id="probe"></div> </div> <script> // expose a small API for tests to read layout and probe elementFromPoint window.testAPI = { getRects() { const box = document.getElementById('box'); const card = document.getElementById('card'); const b = box.getBoundingClientRect(); const c = card.getBoundingClientRect(); return {box: b.toJSON(), card: c.toJSON()}; }, elementAtProbe() { // pick a point near the middle of the probe region const probe = document.getElementById('probe'); const r = probe.getBoundingClientRect(); const x = Math.floor(r.left + r.width/2); const y = Math.floor(r.top + r.height/2); const el = document.elementFromPoint(x, y); return {tag: el ? el.tagName : null, id: el ? el.id || null : null, class: el ? el.className : null}; }, tryScrollBox(amount) { const box = document.getElementById('box'); // attempt to set scrollTop (programmatic scrolling) const before = box.scrollTop; try { box.scrollTop = amount; } catch(e) { /* ignore */ } const after = box.scrollTop; return {before, after}; } }; </script> </body> </html>`; }; Read the full article: https://chriseugenerodriguez.com/blog/overflow-clip-margin-decorative-paint
