DEV Community

sunah kim
sunah kim

Posted on Originally published at zenn.dev

The Unclickable Hamburger Button: Three Wrong Fixes Before I Found the CSS Bundle Order Bug

The report

QA filed this against our internal business web app (React + Vite):

The three-line icon next to the date can't be clicked

One screenshot attached. No device, no screen width, no description of what actually happened on click. From that single line, I fixed the wrong thing three times in a row — and the real culprit turned out to be the order of rules inside the production CSS bundle. Here's the whole detour, as it happened.

Context: there are two hamburger buttons

The sidebar behaves differently depending on viewport width:

  • Desktop (>900px): the sidebar is always visible. A "≡" button in the sidebar header collapses it into a 60px icon rail (the state is persisted in localStorage)
  • Narrow (≤900px): the sidebar becomes a drawer, and a "≡" appears in the app bar (next to the date) to open it

So there are two "≡" buttons, and the app-bar one is hidden on desktop via CSS:

.sidebar-toggle-mobile { display: none; }

@media (max-width: 900px) {
  .sidebar-toggle-mobile { display: inline-flex; }
}
Enter fullscreen mode Exit fullscreen mode

The "three-line icon next to the date" in the report is the app-bar button. I immediately assumed the reporter was on a phone. That assumption was mistake number one.

Wrong fix #1: the collapsed state in localStorage

At 375px locally, everything worked. But when I set "collapsed=true" in localStorage, I could reproduce a state where the drawer opened only as a 60px rail — which does look like "nothing happens."

Convinced I'd found it, I shipped a fix that ignores the collapsed flag at narrow widths. The reporter's reply: "Same as before."

Wrong fix #2: the collapse button inside the drawer

Next I noticed the drawer still contained the desktop collapse "≡", which either shrank the drawer to a rail or did nothing. I replaced it with an "×" (close) button and hid the app-bar "≡" while the drawer was open. Second deploy.

The reporter answered:

I don't understand why there's a ≡ here at all

That one sentence was the decisive clue: they were seeing a "≡" outside drawer mode. And then came the detail that cracked it open:

I'm looking at this on a 16-inch MacBook

Desktop width. The button that display: none was supposed to hide was visible on a wide screen.

The real cause: rule order in the bundle

I downloaded the deployed production CSS bundle and located the two rules by byte offset:

.sidebar-toggle-mobile{display:none}   → at byte 8,975
.icon-btn{display:inline-flex}         → at byte 72,008
Enter fullscreen mode Exit fullscreen mode

.icon-btn is a shared icon-button class, and this "≡" has it too. Both selectors are a single class, so their specificity is identical — and on a tie, the later rule wins. display: inline-flex was overriding display: none.

Why only in production? The sidebar.css imported by the component ended up earlier in the bundle than the entry point's index.css. An order that was harmless on the dev server flipped in the production build.

Suddenly every observation lined up. On desktop, the "≡" is visible. Clicking it does update state — but the drawer and the backdrop are both disabled by CSS at desktop width, so nothing on screen changes. "It can't be clicked" was a perfectly accurate observation.

The fix: a selector that doesn't depend on bundle order

The fix is one line — add a parent class to raise specificity:

.app-bar .sidebar-toggle-mobile { display: none; }

@media (max-width: 900px) {
  .app-bar .sidebar-toggle-mobile { display: inline-flex; }
}
Enter fullscreen mode Exit fullscreen mode

Two classes beat one, wherever the rule lands in the bundle.

Verification was done against production-like builds: I served a local production build and confirmed the computed display was none at 1400px and flex at 375px. After deploying, I downloaded the actually-served CSS bundle again and grepped it to confirm the new rule was in.

Takeaways

  • Dev builds and production bundles can order CSS differently. When something is "only broken in production," look at the deployed bundle itself
  • Don't fill in the reporter's assumptions yourself. "It's a hamburger icon, so it must be a phone" is where the detour started. Ask for device, viewport width, and what exactly appeared on click — first
  • "Correct" fixes are the most dangerous ones. Each of my three fixes was a legitimate improvement in isolation. None of them fixed the reported problem

The decisive clue came from conversation, not code. The three questions to ask before attempting to reproduce: which device, which viewport width, and what exactly did you see when you clicked? Next time, I'm asking those first.

Top comments (1)

Collapse
 
svgicons profile image
Svg/icons •

The .icon-btn class is the sneaky part here. Hard to spot until you inspect the actual bundled CSS.