My nav dropdown stayed open when it should have closed. It felt haunted because the UI kept acting like it was still open even after I clicked somewhere else. The bug wasn’t styling. It wasn’t Gatsby. It was state that never got reset.

This post is a start-to-finish fix you can actually verify inside this repo. The goal is simple: when a dropdown is open, it must close when you click outside of it, and it must also close if the nav itself becomes hidden or collapsed. If either of those are missing, you get the classic “ghost menu” where state says open even though the user is done with it.

What’s really happening

A dropdown is not a DOM thing. In React it is almost always a boolean. If that boolean stays true, the dropdown stays “open” forever unless you explicitly change it back. Clicking outside does not magically update your state. React has no idea you want the menu closed unless you wire up an event that tells it.

The second half of the problem is visibility changes. If your nav collapses on mobile, or hides on scroll, or mounts and unmounts during route transitions, your dropdown state can survive in the wrong moment. That’s where it starts looking haunted. The UI is doing exactly what you told it to do, you just forgot to tell it when to stop.

The fix in one sentence

You add a document-level click listener that closes the dropdown when the click happens outside the dropdown, and you add a second effect that forces the dropdown closed whenever the nav visibility state goes false.

What you need before you touch anything

You need this repo running locally and you need the navigation component open in your editor. Nothing else. This is not a redesign and it is not a refactor. You are adding two small effects and attaching a ref to the correct element.

Start the dev server so you can verify every change as you make it.

bun run dev

If you’re using npm/yarn/pnpm, use whatever starts your Gatsby dev server. The important part is that you can reproduce the bug on demand.

Reproduce the bug first

Before fixing anything, reproduce it in the browser so you know exactly what “broken” looks like.

Open the site, open the dropdown, and then click somewhere else on the page. If the dropdown stays open, or if it closes visually but the state is still stuck (you can usually tell because it reopens weirdly or won’t toggle correctly), you’re looking at the same class of bug.

Now collapse or hide the nav (mobile menu, scroll-hide behavior, whatever your setup is). If the dropdown state survives into the hidden nav state, you’ll see the menu behave out of sync when the nav returns.

That’s the baseline.

Start to finish

Step 1: Close on outside click

This step fixes the most common reason dropdowns get stuck: nothing is watching for clicks that happen outside the menu.

File: src/@lekoarts/gatsby-theme-minimal-blog/components/navigation.tsx

Add a ref that points at the dropdown container element, then register a mousedown listener on document. We use mousedown instead of click because it avoids a common edge case where the opening click and closing click fight each other. The listener checks whether the click happened inside the dropdown container. If it did not, it closes the dropdown.

Add or confirm this effect exists:

const dropdownRef = React.useRef<HTMLLIElement>(null);
React.useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setDropdownOpen(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);

Now the critical part: the ref must be attached to the right DOM element. If you attach it to the wrong node, every click looks “outside” and the dropdown becomes unusable, or the outside click never registers and the dropdown never closes.

Make sure the dropdown container uses the ref:

<li ref={dropdownRef}>

Verification:

Open the dropdown and click anywhere else on the page.

Expected result: the dropdown closes immediately and reliably.

If it fails, don’t guess. It is almost always one of these two issues: the ref is attached to the wrong element, or the dropdown content is rendered outside of that element (portals, absolute-positioned nodes rendered elsewhere, etc.). In this theme setup it is typically just the wrong element.

Step 2: Reset when the nav hides

Outside click fixes the “I’m done with the dropdown” moment, but it doesn’t fix the “nav changed state” moment. If your nav hides or collapses, keeping dropdown open state around is a mistake. The dropdown should not exist as open when the nav is not visible.

File: src/@lekoarts/gatsby-theme-minimal-blog/components/navigation.tsx

Add or confirm this effect exists:

React.useEffect(() => {
if (!isVisible) {
setDropdownOpen(false);
}
}, [isVisible]);

This is intentionally blunt. If visibility goes false, you force the dropdown closed. That prevents ghost UI states and focus traps, and it keeps the toggle logic sane when the nav comes back.

Verification:

Trigger whatever behavior makes your nav not visible. If that is a mobile collapse, do that. If it hides on scroll, scroll until it hides.

Expected result: the dropdown is closed and stays closed when the nav returns.

If it fails, the fix is not to tweak this effect. The fix is to confirm that isVisible is actually the visibility signal you think it is and that it updates when the nav hides.

Verify it worked

You’re done when all three of these feel boring:

The dropdown closes when you click outside. The dropdown closes when the nav hides. The dropdown toggles normally without getting into a stuck half-state.

If you want to be extra strict, do a full pass of “open dropdown → click outside → open dropdown again → collapse nav → expand nav → open dropdown again.” If that loop stays consistent, the bug is gone.

Why this bug keeps showing up

This bug shows up because dropdowns are state machines pretending to be UI. A dropdown is either open or closed. If you do not define the transitions that lead back to closed, state will sit open forever. Outside click and visibility reset are just two transitions people forget to define.

In Gatsby specifically, nav components often live across route transitions and layout changes. That means state can outlive the visual moment that created it. The fix is not Gatsby-specific. It is the same fix you’d ship in any React app.

Optional upgrades that are worth doing

If you want to finish the job, there are two small upgrades that are actually useful.

First, add Escape-to-close. This is a real usability win and it takes a few lines. Second, confirm focus behavior so keyboard users don’t get trapped in a hidden menu.

I’m not including those changes here because the post is about the stuck dropdown bug, but if you’re already in this component, they’re worth the extra minute.

Related reference

Closing

Fix the close behavior first. Fancy styling can wait. A dropdown that can’t close is not a visual issue, it’s a state issue, and this is the smallest reliable set of transitions that keeps it sane.