Prerequisites
- Basic familiarity with CSS overflow values and the distinction between layout vs paint.
- A component that must not become scrollable (cards, badges, decorative wrappers).
Implementation decision
Keep overflow: clip on the wrapper to assert a hard, non-scrollable boundary. If a small decorative paint (shadow, glow, focus ring) must remain visible outside the box, add overflow-clip-margin with the pixel value that matches the effect’s spread. This expands the paint clipping edge but does not create scrolling behavior.
Expected result
- Decorations such as a 20px glow will be visible outside the container up to the clip margin.
- Programmatic scrolling (setting scrollTop) remains ineffective; the element stays non-scrollable.
- The layout stays stable: no scrollbars or scroll anchoring are introduced by the clip margin.
One limitation
Only use this for purely decorative paint. If the overflow contains interactive controls or content that users must access, make the element a true scroll container (overflow: auto/scroll) or revise the layout. overflow-clip-margin is an optical allowance, not an accessibility fix.
The server will insert a verified implementation snippet for the minimal fixture so you can try the pattern and visually validate it in your target browsers.
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