The QA report
"When the sidebar is collapsed, the '+ Create' menu gets cut off."
The screenshot showed exactly that: with the sidebar collapsed to 60px, the dropdown menu was sliced clean off at the sidebar's right border. Only the first character of "New folder" was visible.
The strange part: with the sidebar expanded (244px), the same menu rendered fine. Same code, same 190px menu width. It only broke when collapsed.
Where I got stuck
My first suspect was z-index — maybe the menu was hiding under something. But DevTools showed the menu was in the DOM with a plenty-high z-index. It wasn't hidden; it was being physically clipped at the sidebar's boundary.
Next suspect: overflow. But all the sidebar had was this:
.sidebar {
width: 60px; /* collapsed; 244px when expanded */
overflow-y: auto; /* vertical scroll for long item lists */
}
No overflow-x anywhere. "The horizontal axis is still visible, so anything sticking out should show" — or so I thought.
The cause: one non-visible axis drags the other along
The CSS spec has this rule:
When one of
overflow-x/overflow-yis set to something other thanvisible, avisiblevalue on the other axis computes toauto.
So the moment you write overflow-y: auto, the unspecified overflow-x silently becomes auto. Sure enough, the Computed tab in DevTools showed overflow-x: auto.
Now everything added up:
- The menu was floated inside the sidebar with
position: absolute - The sidebar was effectively an
overflow: auto autoscroll container - A 190px menu inside a 60px container → the horizontal overflow gets clipped (or scrolled)
It had survived in the expanded state only because 244px happened to fit most of the menu, so the clipping wasn't noticeable. Structurally, it was doomed to clip from day one.
The fix: position: fixed only while collapsed
I couldn't drop overflow-y: auto — the scroll is needed. So instead, while collapsed, the menu escapes the scroll container's coordinate system. position: fixed positions against the viewport, so ancestor overflow can't clip it (caveat: an ancestor with transform etc. changes the containing block).
The position comes from the button's getBoundingClientRect(). In React, I store the coordinates in state on click and feed them into style:
const [menuPos, setMenuPos] = useState<{ top: number; left: number } | null>(null);
const handleOpen = (e: React.MouseEvent<HTMLButtonElement>) => {
if (collapsed) {
const rect = e.currentTarget.getBoundingClientRect();
// float it just to the right of the button
setMenuPos({ top: rect.top, left: rect.right + 8 });
}
setOpen(true);
};
<ul
className={collapsed ? "menu menu--fixed" : "menu menu--absolute"}
style={collapsed && menuPos ? { top: menuPos.top, left: menuPos.left } : undefined}
>
.menu--absolute {
position: absolute; /* expanded: as before */
top: 100%;
left: 0;
}
.menu--fixed {
position: fixed; /* collapsed: escape the sidebar */
}
I kept the expanded state on the old absolute path. Moving everything to fixed was tempting, but the expanded state has existing scroll-following behavior tangled in, so I limited the blast radius to the collapsed state.
To verify, I opened the menu in the collapsed state on a local build and compared rectangles in the console:
const menu = document.querySelector(".menu--fixed").getBoundingClientRect();
const sidebar = document.querySelector(".sidebar").getBoundingClientRect();
console.log(menu.left >= sidebar.right); // true → the menu is outside the sidebar
console.log(menu.right, document.documentElement.clientWidth); // still within the viewport?
menu.left sat to the right of the sidebar's edge and the whole menu fit in the viewport. Done.
Takeaways
Two lessons from this one.
1. A popover inside a scroll container will eventually get clipped.
The instant you write overflow-y: auto, that element becomes a container that clips horizontally too. Any dropdown or tooltip floated inside it with absolute gets cut the moment it outgrows the container. "It looks fine now" just means it happens to fit today. Put popovers on fixed or in a Portal — outside the scroll container — from the start.
2. Auxiliary states like "collapsed" deserve one pass per feature.
That same day we got three bug reports tied to the collapsed state: a drawer opening as a 60px sliver, the hamburger icon showing at desktop width, and this clipped menu. All of them worked perfectly when expanded and broke the moment you collapsed. We had only ever tested the default state. Of all the state combinations (expanded × collapsed, light × dark, desktop × mobile), the side you don't personally use is exactly the one worth a full lap.
Hope this saves someone the same detour.
Top comments (0)