[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"quiz-overflow-clip-vs-overflow-hidden-sticky":3,"verticals":25,"quiz-article-overflow-clip-vs-overflow-hidden-sticky":60,"path-membership-overflow-clip-vs-overflow-hidden-sticky":226,"search-suggestions":227},{"id":4,"slug":5,"kind":6,"title":7,"description":8,"config":9,"verticalId":17,"vertical":18,"course":11,"_count":21,"access":22,"attempts":24,"questionCount":10},"01a1c0f9-dd03-746f-8049-6c24d8601fdc","overflow-clip-vs-overflow-hidden-sticky","PRACTICE_QUIZ","overflow: clip vs overflow: hidden — and the sticky bar they break","Seven questions on why overflow: hidden can quietly break a nested position: sticky element, and why overflow: clip usually doesn't.",{"questionCount":10,"timeLimitSec":11,"shuffleQuestions":12,"shuffleOptions":13,"negativeMarking":14,"passScorePct":15,"maxAttempts":11,"revealAnswers":16,"allowFlagging":12,"allowBacktracking":13},7,null,false,true,0,70,"AFTER_SUBMIT","019fe637-3d33-714b-b57f-23e163ffca0c",{"slug":19,"name":20},"dev","Web Development",{"questions":10},{"allowed":13,"reason":23},"FREE",[],[26,36,48],{"id":17,"slug":19,"name":20,"tagline":27,"description":28,"accentFrom":29,"accentTo":30,"icon":31,"defaultLocale":32,"locales":33,"features":35,"position":14},"Read it. Run it. Prove it.","A post a day on modern web development — most with an editable playground and a quiz that explains every answer. Free, no account needed.","violet-500","cyan-400","◇","en",[32,34],"fa",{"courses":13,"paths":13,"articles":13,"exams":12,"flashcards":12,"packages":13,"community":13,"certificates":13,"teams":13,"commerce":13},{"id":37,"slug":38,"name":39,"tagline":40,"description":41,"accentFrom":42,"accentTo":29,"icon":43,"defaultLocale":32,"locales":44,"features":46,"position":47},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Grammar explained the way good documentation explains an API — one idea at a time, each with a quiz.","amber-400","⌘",[32,34,45],"es",{"courses":12,"paths":12,"articles":13,"exams":12,"flashcards":13,"packages":12,"community":13,"certificates":13,"teams":12,"commerce":12},2,{"id":49,"slug":50,"name":51,"tagline":52,"description":53,"accentFrom":54,"accentTo":55,"icon":56,"defaultLocale":32,"locales":57,"features":58,"position":59},"7b3c16f2-931d-410e-802e-e1fa4edab7de","soft","Soft Skills","The half of the job nobody wrote documentation for.","Weekly, on the parts of working life that decide more than your code does — first weeks, meetings, interviews, promotions, and the people around you. Written from what actually happens, and recorded as a podcast you can listen to on the walk.","emerald-400","teal-300","◉",[32],{"courses":12,"paths":12,"articles":13,"exams":12,"flashcards":12,"packages":12,"community":13,"certificates":12,"teams":12,"commerce":12},3,{"id":61,"slug":5,"title":62,"subtitle":11,"excerpt":63,"coverUrl":64,"locale":32,"readingMinutes":65,"publishedAt":66,"viewCount":67,"likeCount":14,"commentCount":14,"author":68,"vertical":73,"topic":74,"tags":77,"_count":89,"playground":91,"body":93,"bodyMd":212,"seo":213,"translationGroupId":216,"series":11,"podcastUrl":11,"verticalId":17,"thread":217,"assessments":219,"translations":222,"quiz":224},"01a1c0f9-dcbd-71ce-acad-1012bf44027c","overflow: hidden breaks position: sticky. overflow: clip doesn't.","A sticky header that worked in isolation stops sticking the moment it's dropped inside a rounded-corner card with overflow: hidden. The card isn't scrollable — but the browser doesn't care. overflow: clip is the fix, and it's been quietly ready for a while.","\u002Fmedia\u002Fcovers\u002Foverflow-clip-vs-overflow-hidden-sticky.png",4,"2026-11-15T12:07:47.081Z",23,{"id":69,"name":70,"username":71,"avatarUrl":11,"headline":72},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":19,"name":20,"accentFrom":29,"accentTo":30},{"slug":75,"name":76},"css","CSS",[78,80,83,86],{"slug":75,"name":79,"color":11},"Css",{"slug":81,"name":82,"color":11},"webdev","Webdev",{"slug":84,"name":85,"color":11},"frontend","Frontend",{"slug":87,"name":88,"color":11},"tutorial","Tutorial",{"assessments":90},1,{"slug":5,"title":92},"overflow: clip vs overflow: hidden — sticky playground",{"blocks":94,"version":90},[95,99,102,105,109,112,115,120,123,126,130,133,137,140,143,149,152,155,158,161,164,167,170,173,176,179,182,185,188,191,194,197,200,203,206],{"id":96,"html":97,"type":98},"b1","\u003Cp>Someone reports a bug: the &quot;jump to section&quot; bar at the top of a long settings page used to stick to the top of the viewport while you scrolled. Now it doesn&#39;t. It just sits there in normal flow like \u003Ccode>position: sticky\u003C\u002Fcode> was never applied.\u003C\u002Fp>","paragraph",{"id":100,"html":101,"type":98},"b2","\u003Cp>You open devtools. The CSS is untouched — \u003Ccode>position: sticky; top: 0;\u003C\u002Fcode>, same as it&#39;s been for months. No typo, no overridden rule, nothing crossed out in the inspector. The element is exactly as sticky as it was yesterday. Except it isn&#39;t sticking.\u003C\u002Fp>",{"id":103,"html":104,"type":98},"b3","\u003Cp>The actual change shipped in a different file entirely: a shared \u003Ccode>&lt;Card&gt;\u003C\u002Fcode> component picked up \u003Ccode>overflow: hidden\u003C\u002Fcode> last week, to stop a decorative background image from spilling past its rounded corners. Nobody touched the settings page. Nobody touched the sticky bar. And yet here we are.\u003C\u002Fp>",{"id":106,"html":107,"text":107,"type":108,"level":47},"b4","The rule nobody reads until it bites them","heading",{"id":110,"html":111,"type":98},"b5","\u003Cp>\u003Ccode>position: sticky\u003C\u002Fcode> doesn&#39;t stick to the viewport by default. It sticks to its \u003Cstrong>nearest scroll container\u003C\u002Fstrong> — the closest ancestor whose overflow isn&#39;t \u003Ccode>visible\u003C\u002Fcode>. Usually that&#39;s the page itself, so sticky behaves exactly like you&#39;d expect: pinned to the window as you scroll.\u003C\u002Fp>",{"id":113,"html":114,"type":98},"b6","\u003Cp>But &quot;nearest ancestor with non-\u003Ccode>visible\u003C\u002Fcode> overflow&quot; doesn&#39;t check whether that ancestor actually scrolls. It just checks the computed \u003Ccode>overflow\u003C\u002Fcode> value. Set \u003Ccode>overflow: hidden\u003C\u002Fcode> anywhere between the sticky element and the page — for \u003Cem>any\u003C\u002Fem> reason, clipping an image, trimming a shadow, stopping an accidental horizontal scrollbar — and that element becomes the sticky container instead of the viewport.\u003C\u002Fp>",{"id":116,"code":117,"type":118,"language":75,"highlight":119},"b7",".card {\n  border-radius: 16px;\n  overflow: hidden; \u002F* so the thumbnail respects the rounded corner *\u002F\n}\n\n.card .jump-bar {\n  position: sticky;\n  top: 0; \u002F* now relative to .card, not the page *\u002F\n}","code",[],{"id":121,"html":122,"type":98},"b8","\u003Cp>\u003Ccode>.card\u003C\u002Fcode> never scrolls — its content fits. So the sticky bar&#39;s &quot;scroll container&quot; never moves, which means the bar never has a reason to detach from its normal spot and pin anywhere. Visually: it just looks broken. There&#39;s no console warning, no deprecation notice, nothing that points back to a \u003Ccode>border-radius\u003C\u002Fcode> fix in an unrelated component.\u003C\u002Fp>",{"id":124,"html":125,"type":98},"b9","\u003Cp>Try it before you keep reading: open your own devtools on any sticky element that&#39;s misbehaving and check \u003Ccode>getComputedStyle\u003C\u002Fcode> on each ancestor for \u003Ccode>overflow\u003C\u002Fcode>. It&#39;s almost always \u003Ccode>hidden\u003C\u002Fcode>, and it&#39;s almost always there for a reason that has nothing to do with scrolling.\u003C\u002Fp>",{"id":127,"html":128,"text":129,"type":108,"level":47},"b10","The fix that doesn&#39;t ask you to restructure anything","The fix that doesn't ask you to restructure anything",{"id":131,"html":132,"type":98},"b11","\u003Cp>\u003Ccode>overflow: clip\u003C\u002Fcode> looks, on paper, like a quieter synonym for \u003Ccode>hidden\u003C\u002Fcode>. It clips content at the element&#39;s padding edge the same way. The difference is in what it refuses to become: the CSS Overflow spec defines \u003Ccode>clip\u003C\u002Fcode> so that it never creates a scroll container. There&#39;s no scrollport to establish, so nothing can be scrolled into it — not by the user, not by \u003Ccode>element.scrollTop = 40\u003C\u002Fcode> in your own code. \u003Ccode>hidden\u003C\u002Fcode> still lets that second one through, which is exactly the mechanism it uses to qualify as scrollable in the first place.\u003C\u002Fp>",{"id":134,"code":135,"type":118,"language":75,"highlight":136},"b12",".card {\n  border-radius: 16px;\n  overflow: clip; \u002F* clips the same, isn't a scroll container *\u002F\n}",[],{"id":138,"html":139,"type":98},"b13","\u003Cp>Swap the one keyword and the sticky bar starts looking straight past \u003Ccode>.card\u003C\u002Fcode> again, up to the real scroll container — the page. Nothing else in the component changes. No wrapper \u003Ccode>&lt;div&gt;\u003C\u002Fcode>, no \u003Ccode>position: relative\u003C\u002Fcode> hack, no JavaScript recalculating offsets on scroll.\u003C\u002Fp>",{"id":141,"html":142,"type":98},"b14","\u003Cp>Two details worth knowing before you reach for it everywhere:\u003C\u002Fp>",{"id":144,"type":145,"items":146,"ordered":12},"b15","list",[147,148],"\u003Cstrong>It&#39;s independent per axis.\u003C\u002Fstrong> \u003Ccode>overflow-x: clip; overflow-y: visible;\u003C\u002Fcode> does what you&#39;d want. Try that with \u003Ccode>hidden\u003C\u002Fcode> and the browser forces the other axis to \u003Ccode>auto\u003C\u002Fcode> for you, which is its own long-running source of surprise scrollbars.","\u003Cstrong>\u003Ccode>overflow-clip-margin\u003C\u002Fcode>\u003C\u002Fstrong> lets clipped content bleed a few pixels past the box before it&#39;s cut — useful when a focus ring or a shadow would otherwise get sheared off exactly at the edge.",{"id":150,"html":151,"type":98},"b16","\u003Cp>The one thing \u003Ccode>overflow: clip\u003C\u002Fcode> can&#39;t do that \u003Ccode>hidden\u003C\u002Fcode> can is be scrolled at all — if that container is genuinely meant to scroll its own content (a chat panel, a code block with a max-height), \u003Ccode>hidden\u003C\u002Fcode> paired with a real scrollbar is still the right tool. \u003Ccode>clip\u003C\u002Fcode> is for the much more common case: an element that clips for layout reasons and was never supposed to be a scroll container in the first place.\u003C\u002Fp>",{"id":153,"html":154,"type":98},"b17","\u003Cp>Play with the before\u002Fafter below — flip \u003Ccode>.card\u003C\u002Fcode> between \u003Ccode>hidden\u003C\u002Fcode> and \u003Ccode>clip\u003C\u002Fcode> and watch the sticky bar inside it react in real time.\u003C\u002Fp>",{"id":156,"html":157,"type":98},"b18","\u003C!-- playground:start -->",{"id":159,"html":160,"text":160,"type":108,"level":47},"b19","🎮 Try it yourself",{"id":162,"html":163,"type":98},"b20","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Foverflow-clip-vs-overflow-hidden-sticky\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":165,"html":166,"type":98},"b21","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":168,"html":169,"type":98},"b22","\u003C!-- playground:end -->",{"id":171,"html":172,"text":172,"type":108,"level":47},"b23","The part worth remembering",{"id":174,"html":175,"type":98},"b24","\u003Cp>\u003Ccode>overflow: hidden\u003C\u002Fcode> was never just &quot;clip this.&quot; It was always &quot;clip this, and also: this now scrolls, even if you never intended that and nothing inside it ever moves.&quot; Every \u003Ccode>position: sticky\u003C\u002Fcode> bug report that starts with &quot;it worked until I touched a totally unrelated component&quot; is worth checking against exactly that rule before you touch the sticky declaration at all.\u003C\u002Fp>",{"id":177,"html":178,"type":98},"b25","\u003Cp>\u003Ccode>overflow: clip\u003C\u002Fcode> separates the two meanings that \u003Ccode>hidden\u003C\u002Fcode> always bundled together. If you only wanted the first one, it&#39;s the one to reach for now.\u003C\u002Fp>",{"id":180,"html":181,"type":98},"b26","\u003Cp>Next time a sticky element goes dead for no visible reason, what&#39;s the first ancestor you check — and how far up the tree did the real culprit turn out to be last time?\u003C\u002Fp>",{"id":183,"html":184,"type":98},"b27","\u003C!-- quiz:start -->",{"id":186,"html":187,"text":187,"type":108,"level":47},"b28","🧠 Test yourself",{"id":189,"html":190,"type":98},"b29","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Foverflow-clip-vs-overflow-hidden-sticky\u002Fquiz\">Take the 7-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":192,"html":193,"type":98},"b30","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":195,"html":196,"type":98},"b31","\u003C!-- quiz:end -->",{"id":198,"type":199},"b32","divider",{"id":201,"html":202,"type":98},"b33","\u003Cp>🚀 \u003Cstrong>Want more like this?\u003C\u002Fstrong> Every guide, playground, and quiz lives on \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">bestpractic.org\u003C\u002Fa>\u003C\u002Fstrong> — open it and \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">sign up free\u003C\u002Fa>\u003C\u002Fstrong> so the next one finds you.\u003C\u002Fp>",{"id":204,"html":205,"type":98},"b34","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":207,"type":145,"items":208,"ordered":12},"b35",[209,210,211],"⭐ \u003Cstrong>GitHub\u003C\u002Fstrong> — follow me and star the projects: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fparsajiravand\">github.com\u002Fparsajiravand\u003C\u002Fa>","💬 \u003Cstrong>Discord\u003C\u002Fstrong> — join the frontend best-practices community: \u003Ca href=\"https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ\">discord.gg\u002Fd9KRhuAwQ\u003C\u002Fa>","📸 \u003Cstrong>Instagram\u003C\u002Fstrong> — frontend best practices, daily: \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F\">@bestpractice___\u003C\u002Fa>","Someone reports a bug: the \"jump to section\" bar at the top of a long settings page used to stick to the top of the viewport while you scrolled. Now it doesn't. It just sits there in normal flow like `position: sticky` was never applied.\n\nYou open devtools. The CSS is untouched — `position: sticky; top: 0;`, same as it's been for months. No typo, no overridden rule, nothing crossed out in the inspector. The element is exactly as sticky as it was yesterday. Except it isn't sticking.\n\nThe actual change shipped in a different file entirely: a shared `\u003CCard>` component picked up `overflow: hidden` last week, to stop a decorative background image from spilling past its rounded corners. Nobody touched the settings page. Nobody touched the sticky bar. And yet here we are.\n\n## The rule nobody reads until it bites them\n\n`position: sticky` doesn't stick to the viewport by default. It sticks to its **nearest scroll container** — the closest ancestor whose overflow isn't `visible`. Usually that's the page itself, so sticky behaves exactly like you'd expect: pinned to the window as you scroll.\n\nBut \"nearest ancestor with non-`visible` overflow\" doesn't check whether that ancestor actually scrolls. It just checks the computed `overflow` value. Set `overflow: hidden` anywhere between the sticky element and the page — for *any* reason, clipping an image, trimming a shadow, stopping an accidental horizontal scrollbar — and that element becomes the sticky container instead of the viewport.\n\n```css\n.card {\n  border-radius: 16px;\n  overflow: hidden; \u002F* so the thumbnail respects the rounded corner *\u002F\n}\n\n.card .jump-bar {\n  position: sticky;\n  top: 0; \u002F* now relative to .card, not the page *\u002F\n}\n```\n\n`.card` never scrolls — its content fits. So the sticky bar's \"scroll container\" never moves, which means the bar never has a reason to detach from its normal spot and pin anywhere. Visually: it just looks broken. There's no console warning, no deprecation notice, nothing that points back to a `border-radius` fix in an unrelated component.\n\nTry it before you keep reading: open your own devtools on any sticky element that's misbehaving and check `getComputedStyle` on each ancestor for `overflow`. It's almost always `hidden`, and it's almost always there for a reason that has nothing to do with scrolling.\n\n## The fix that doesn't ask you to restructure anything\n\n`overflow: clip` looks, on paper, like a quieter synonym for `hidden`. It clips content at the element's padding edge the same way. The difference is in what it refuses to become: the CSS Overflow spec defines `clip` so that it never creates a scroll container. There's no scrollport to establish, so nothing can be scrolled into it — not by the user, not by `element.scrollTop = 40` in your own code. `hidden` still lets that second one through, which is exactly the mechanism it uses to qualify as scrollable in the first place.\n\n```css\n.card {\n  border-radius: 16px;\n  overflow: clip; \u002F* clips the same, isn't a scroll container *\u002F\n}\n```\n\nSwap the one keyword and the sticky bar starts looking straight past `.card` again, up to the real scroll container — the page. Nothing else in the component changes. No wrapper `\u003Cdiv>`, no `position: relative` hack, no JavaScript recalculating offsets on scroll.\n\nTwo details worth knowing before you reach for it everywhere:\n\n- **It's independent per axis.** `overflow-x: clip; overflow-y: visible;` does what you'd want. Try that with `hidden` and the browser forces the other axis to `auto` for you, which is its own long-running source of surprise scrollbars.\n- **`overflow-clip-margin`** lets clipped content bleed a few pixels past the box before it's cut — useful when a focus ring or a shadow would otherwise get sheared off exactly at the edge.\n\nThe one thing `overflow: clip` can't do that `hidden` can is be scrolled at all — if that container is genuinely meant to scroll its own content (a chat panel, a code block with a max-height), `hidden` paired with a real scrollbar is still the right tool. `clip` is for the much more common case: an element that clips for layout reasons and was never supposed to be a scroll container in the first place.\n\nPlay with the before\u002Fafter below — flip `.card` between `hidden` and `clip` and watch the sticky bar inside it react in real time.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Foverflow-clip-vs-overflow-hidden-sticky\u002Fplayground)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n## The part worth remembering\n\n`overflow: hidden` was never just \"clip this.\" It was always \"clip this, and also: this now scrolls, even if you never intended that and nothing inside it ever moves.\" Every `position: sticky` bug report that starts with \"it worked until I touched a totally unrelated component\" is worth checking against exactly that rule before you touch the sticky declaration at all.\n\n`overflow: clip` separates the two meanings that `hidden` always bundled together. If you only wanted the first one, it's the one to reach for now.\n\nNext time a sticky element goes dead for no visible reason, what's the first ancestor you check — and how far up the tree did the real culprit turn out to be last time?\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 7-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Foverflow-clip-vs-overflow-hidden-sticky\u002Fquiz)**\n\n_Instant feedback, a hint on every question, and an explanation for each answer — right or wrong._\n\n\u003C!-- quiz:end -->\n\n---\n\n🚀 **Want more like this?** Every guide, playground, and quiz lives on **[bestpractic.org](https:\u002F\u002Fbestpractic.org\u002F)** — open it and **[sign up free](https:\u002F\u002Fbestpractic.org\u002F)** so the next one finds you.\n\n*Thanks for reading! Let's stay connected:*\n\n- ⭐ **GitHub** — follow me and star the projects: [github.com\u002Fparsajiravand](https:\u002F\u002Fgithub.com\u002Fparsajiravand)\n- 💬 **Discord** — join the frontend best-practices community: [discord.gg\u002Fd9KRhuAwQ](https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ)\n- 📸 **Instagram** — frontend best practices, daily: [@bestpractice___](https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F)",{"title":62,"canonical":214,"description":215},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Foverflow-clip-vs-overflow-hidden-sticky","A sticky header that worked in isolation stops sticking the moment it's dropped inside a rounded-corner card with overflow: hidden. The card isn't scrollable — but the browser does","01a1c0f9-dcbd-71ce-acad-15c11ea82ba4",{"id":218,"locked":12},"01a1c0f9-dcdc-72a5-9fb9-aea5eedc9bf2",[220],{"id":4,"slug":5,"title":7,"_count":221},{"questions":10},[223],{"locale":32,"slug":5},{"id":4,"slug":5,"title":7,"_count":225,"questionCount":10},{"questions":10},[],[228,230,234,236,238,240,244,248,252,255,259,263],{"slug":81,"name":82,"articles":229},177,{"slug":231,"name":232,"articles":233},"javascript","Javascript",142,{"slug":84,"name":85,"articles":235},98,{"slug":87,"name":88,"articles":237},72,{"slug":75,"name":79,"articles":239},53,{"slug":241,"name":242,"articles":243},"performance","Performance",31,{"slug":245,"name":246,"articles":247},"typescript","Typescript",25,{"slug":249,"name":250,"articles":251},"react","React",20,{"slug":253,"name":254,"articles":251},"programming","Programming",{"slug":256,"name":257,"articles":258},"browser","Browser",18,{"slug":260,"name":261,"articles":262},"node","Node",16,{"slug":264,"name":265,"articles":266},"html","Html",12]