DEV Community

sunah kim
sunah kim

Posted on Originally published at zenn.dev

The Clipped Dropdown in a Collapsed Sidebar — the Hidden Meaning of overflow-y: auto

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 */
}
Enter fullscreen mode Exit fullscreen mode

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-y is set to something other than visible, a visible value on the other axis computes to auto.

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 auto scroll 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);
};
Enter fullscreen mode Exit fullscreen mode
<ul
  className={collapsed ? "menu menu--fixed" : "menu menu--absolute"}
  style={collapsed && menuPos ? { top: menuPos.top, left: menuPos.left } : undefined}
>
Enter fullscreen mode Exit fullscreen mode
.menu--absolute {
  position: absolute; /* expanded: as before */
  top: 100%;
  left: 0;
}
.menu--fixed {
  position: fixed; /* collapsed: escape the sidebar */
}
Enter fullscreen mode Exit fullscreen mode

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?
Enter fullscreen mode Exit fullscreen mode

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)