overflow: clip saved my navbar
DEV Community

overflow: clip saved my navbar

This is a submission for DEV's Summer Bug Smash: Smash Stories powered by Sentry. There was no error in the console, the CSS was syntactically correct, the logic was sound, and overflow: hidden was just not working. I was building the navigation overlay for my personal portfolio, which runs on Next.js 15 with App Router, React 19 and Tailwind v4. The navbar opens a fullscreen overlay that descends from the top using GSAP. Inside that overlay, each navigation link has a split reveal effect: the link text sits in white with a dark layer underneath it, and on hover the dark layer rises to cover the white text while dark-colored text climbs up from below to replace it. The classic typographic flip that you see in agency sites and high-end portfolios. The overlay itself had position: fixed to cover the entire viewport. That part worked fine. The text effect required wrapping each link in a container with overflow: hidden so that the rising dark layer and the incoming dark text would be clipped until they entered the visible area. Without that clip, both layers show simultaneously and the menu looks like every link is doubled in two colors, which is exactly what I was seeing. Six things that didn't fix it The first attempt was overflow: hidden directly on the link wrapper container, but nothing changed and both text layers stayed visible. .nav-link-wrapper { overflow: hidden; /* did nothing / } I switched to a fixed pixel height on the container calculated manually to match the line height. The thinking was that maybe the browser needed an explicit dimension to clip against and... still nothing. Then I tried clip-path: inset(0 0 100% 0) animated in CSS, which clips the element's painted area directly without involving overflow behavior at all. The layers kept showing through. I moved overflow: hidden up to the element with no difference, then to the , which made things worse because now it was affecting the whole list. At that point I started suspecting a specificity conflict or a CSS-in-JS issue, so I switched to inline styles in React to eliminate any possible cascade interference: ... Same result, both layers still visible. After six attempts there was no progress, no error messages and nothing in the console pointing anywhere useful. What was actually happening After enough time searching I found the answer in the CSS specification. overflow: hidden does not just clip content, it also creates what the spec calls a Block Formatting Context, which establishes an independent layout environment for the element and its descendants. There's a critical exception to this though: a Block Formatting Context cannot contain elements that are descendants of an ancestor with position: fixed . The overlay had position: fixed . Every element inside it, including my link wrappers trying to use overflow: hidden , existed as a descendant of a fixed-positioned ancestor. When a child element tries to establish a BFC through overflow: hidden but its nearest ancestor with a stacking context is position: fixed , the browser cannot create the expected formatting context correctly. The clipping behavior does not apply. This interaction is defined in the CSS specification. It's not a bug in any browser. Chrome, Firefox and Safari all behave the same way because they're all following the spec. The problem is that overflow: hidden works in probably 99% of cases people use it, so nobody builds a mental model of when it doesn't. There's also nothing diagnostic about the failure. The element renders. The CSS applies. The browser just quietly doesn't clip the overflow because the BFC can't be established in that context. The fix The answer was overflow: clip . .nav-link-wrapper { overflow: clip; / instead of overflow: hidden */ } overflow: clip was introduced in CSS as a more precise clipping mechanism and unlike overflow: hidden it does not create a Block Formatting Context. It clips the painted content visually without establishing any formatting context at all, which means there's no conflict with the position: fixed ancestor and the clipping works exactly as expected, cutting off content that goes outside the element's bounds without any stacking context complications. For the user, overflow: clip and overflow: hidden look identical when they both work. The difference is only in what the browser does internally, and that internal difference is what determines whether clipping actually happens inside a fixed overlay. The text effect works now and the dark layer rises on hover, the white text disappears beneath it, the dark text climbs into view from below, and both layers clip cleanly at the container boundary. Why this one is worth knowing Most CSS bugs announce themselves. A layout breaks visually in an obvious way, or the console flags something, or the behavior changes between browsers in a way that points to a compatibility issue. This one didn't. The code looked right because it was right, just using a property that had a documented edge case that almost no practical documentation mentions. overflow: clip has been supported in all major browsers since 2022, so it's not new, but because overflow: hidden handles the vast majority of clipping scenarios without issue, most developers have never needed to reach for it. If you're building UI where overflow clipping needs to work inside a position: fixed container, whether that's a fullscreen overlay, a fixed sidebar, or a sticky header with animated content inside it, overflow: clip is the right tool. The six failed attempts weren't wasted. Working through clip-path , inline styles and every possible container element confirmed that the problem wasn't specificity, wasn't React, wasn't Tailwind, and wasn't any configuration issue. It was the CSS spec doing exactly what the CSS spec says, in a case where the spec and the intuition don't match. Portfolio built with Next.js 16, React 19 and Tailwind v4. Available at carlosjcastrog.com. Top comments (0)

Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.