Skip to content

fix(MegaMenu): support multiple dropdowns in a single mega menu - #1685

Open
jorj-pineda wants to merge 7 commits into
themesberg:mainfrom
jorj-pineda:fix/mega-menu-multiple-dropdowns
Open

fix(MegaMenu): support multiple dropdowns in a single mega menu#1685
jorj-pineda wants to merge 7 commits into
themesberg:mainfrom
jorj-pineda:fix/mega-menu-multiple-dropdowns

Conversation

@jorj-pineda

@jorj-pineda jorj-pineda commented Aug 12, 2026

Copy link
Copy Markdown

Summary

Fixes the functional half of #1610: a <MegaMenu> containing more than one dropdown only ever operated the first one.

<MegaMenuDropdownToggle> resolved its dropdown with querySelector('[role="menu"]') scoped to the enclosing <nav>. querySelector returns the first match, so every toggle in the navbar controlled the first dropdown. <MegaMenuDropdown> had the mirror-image bug, pointing every panel's aria-labelledby at the first toggle's id.

Changes

  • Pair by declaration order. The n-th toggle of a mega menu now controls its n-th dropdown. Toggles and dropdowns are siblings with no shared wrapper, so declaration order is the only thing linking them — and it matches the markup already used in the docs, where the toggle lives inside <NavbarCollapse> while the dropdown sits outside it.
  • Opening a dropdown closes the others. Tracked through a new MegaMenuContext so each toggle's aria-expanded stays owned by the component that renders it, rather than being written into a sibling's DOM where it could drift from what is actually visible.
  • Fixed conditionally called hooks. <MegaMenuDropdown> called useId, useRef and useEffect after its early return for the toggle variant. The panel branch is now its own component, so the hooks are unconditional.
  • Docs. New "Multiple dropdowns" example on the mega menu page plus a matching Storybook story.

Implementation note

Pairing keys off two internal data-* attributes rather than the ARIA roles. <MegaMenuDropdown toggle={..}> renders a <Dropdown> whose trigger also carries aria-haspopup="menu" and whose floating panel also carries role="menu" — and that panel is only mounted while open. Indexing on the roles would therefore have re-shuffled the pairing every time a sibling dropdown opened or closed.

Breaking API changes

None. No props were added, removed or renamed, and the theme structure is unchanged. The two data-* attributes are inert markers and are not exported.

MegaMenuContext, useMegaMenuContext and MegaMenuContextValue are newly exported, matching how NavbarContext, DropdownContext, SidebarContext and AccordionPanelContext are already part of the public API.

One behavior change worth calling out: with several dropdowns declared, opening one now closes the others. Mount behavior is untouched — a panel declared without a hidden class still renders open on first paint, exactly as before.

Deliberately not included

#1610 also reports that a <MegaMenuDropdown toggle={..}> cannot be reliably centered. That one is not a local bug: className is forwarded to both the trigger and the floating panel, and the panel carries inline positioning styles from Floating UI that a utility class cannot override. Fixing it means an API decision — a separate floatingClassName, a positioning escape hatch, or something else — so I have left it out rather than guess. Happy to follow up in a separate PR if you can indicate the direction you would prefer.

Note also that #1666 already proposes a one-line theme.ts change for the related margin issue. This PR does not touch theme.ts, so the two should not conflict.

Testing

packages/ui/src/components/MegaMenu/MegaMenu.test.tsx gains coverage for two toggle/dropdown pairs:

  • each toggle's aria-controls and each panel's aria-labelledby resolve to their own partner
  • clicking a toggle hides/shows only its own dropdown
  • opening one dropdown closes the other

Both new pairing tests fail on main and pass with this change. The existing single-dropdown tests are unaffected.

The multiple-dropdown isolation test drops two assertions that described the previous non-exclusive behavior on re-open, since that is what the exclusive-open change replaces.

bun run format:check && bun run lint && bun run typecheck && bun run test

Refs #1610

Summary by CodeRabbit

  • New Features

    • MegaMenu now supports multiple independent dropdowns in a single navigation bar.
    • Dropdowns pair by declaration order, even when they are not siblings.
    • Opening one dropdown automatically closes any other open dropdown.
    • Added a responsive example demonstrating multiple dropdown menus and navigation links.
  • Documentation

    • Added usage guidance and examples for configuring multiple MegaMenu dropdowns.
  • Bug Fixes

    • Improved dropdown and toggle pairing, visibility behavior, and accessibility state handling.
  • Tests

    • Added coverage for independent toggling, pairing, and automatic closing behavior.

Add a fixture rendering two MegaMenuDropdownToggle/MegaMenuDropdown
pairs inside a single MegaMenu, covering the scenario reported in themesberg#1610.

Both new tests fail against the current implementation:

- Every toggle resolves its dropdown via querySelector('[role=menu]')
  scoped to the whole <nav>, so all toggles target the first dropdown.
  aria-controls on the second toggle points at the first dropdown's id.
- Clicking the second toggle hides/shows the first dropdown instead of
  its own.

Refs themesberg#1610
MegaMenuDropdownToggle resolved its dropdown with querySelector('[role=menu]')
scoped to the enclosing <nav>, which always returns the first match. Every toggle
in a mega menu therefore controlled the first dropdown, and MegaMenuDropdown had
the mirror-image bug, labelling every panel with the first toggle's id.

Pair them by declaration order instead: the n-th toggle controls the n-th
dropdown. Pairing keys off dedicated data attributes rather than the ARIA roles,
because <MegaMenuDropdown toggle={..}> renders a Dropdown that also carries
aria-haspopup=menu and role=menu — and mounts its panel only while open,
which would shift the indices as sibling dropdowns opened and closed.

Also extract the panel branch of MegaMenuDropdown into its own component. Its
useId/useRef/useEffect calls sat after the early return for the  variant,
so they were called conditionally, breaking the rules of hooks.

Fixes themesberg#1610
Multiple mega menu dropdowns could be expanded at the same time, since each
toggle only ever touched its own dropdown.

Track the currently open toggle in a new MegaMenuContext so a toggle can
collapse itself when a sibling opens. Coordinating through context rather than
reaching into the sibling's DOM keeps each toggle's aria-expanded owned by the
component that renders it, so the attribute cannot drift from what is visible.

The context is only read on interaction, so panels declared without a
class still render open on mount as before. useMegaMenuContext intentionally
returns undefined instead of throwing, because MegaMenuDropdownToggle also
works inside a plain Navbar.

Adjusts the multiple-dropdown test that asserted the previous non-exclusive
behavior on re-open.

Refs themesberg#1610
Every other context in the library is part of the public API
(NavbarContext, DropdownContext, SidebarContext, AccordionPanelContext),
so export MegaMenuContext, useMegaMenuContext and MegaMenuContextValue for
consistency.

The pairing helpers stay internal, matching how Clipboard and Pagination
keep their helpers.ts unexported.

Refs themesberg#1610
Document declaring more than one dropdown in a single mega menu: pairing goes by declaration order, toggles and dropdowns do not have to be siblings, and a "hidden" class makes a panel start closed.

Refs themesberg#1610
Patch bump, matching how this project versions additive changes: minor is
reserved for breaking changes in the changelog history.

Refs themesberg#1610
@changeset-bot

changeset-bot Bot commented Aug 12, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 6e5366b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
flowbite-react Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Aug 12, 2026

Copy link
Copy Markdown

@jorj-pineda is attempting to deploy a commit to the Bergside Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8ff88e7e-6e15-45a8-9738-e1ff4e85dda3

📥 Commits

Reviewing files that changed from the base of the PR and between 8b31da1 and 6e5366b.

📒 Files selected for processing (3)
  • apps/storybook/src/MegaMenu.stories.tsx
  • apps/web/content/docs/components/mega-menu.mdx
  • apps/web/examples/megaMenu/megaMenu.multipleDropdowns.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/web/content/docs/components/mega-menu.mdx
  • apps/storybook/src/MegaMenu.stories.tsx
  • apps/web/examples/megaMenu/megaMenu.multipleDropdowns.tsx

📝 Walkthrough

Walkthrough

MegaMenu now supports multiple declaration-ordered dropdown pairs. Shared context tracks the active toggle and closes other panels. Tests, Storybook, web examples, documentation, and a patch changeset cover the new behavior.

Changes

Multiple MegaMenu dropdowns

Layer / File(s) Summary
Pairing helpers and shared state
packages/ui/src/components/MegaMenu/helpers.ts, packages/ui/src/components/MegaMenu/MegaMenuContext.tsx, packages/ui/src/components/MegaMenu/MegaMenu.tsx, packages/ui/src/components/MegaMenu/index.ts
Added declaration-order pairing helpers, shared open-toggle context, provider state, and public exports.
Dropdown behavior and validation
packages/ui/src/components/MegaMenu/MegaMenuDropdown.tsx, packages/ui/src/components/MegaMenu/MegaMenuDropdownToggle.tsx, packages/ui/src/components/MegaMenu/MegaMenu.test.tsx
Toggles now control paired panels, update ARIA state, close other panels, and support non-sibling placement. Tests cover pairing and visibility behavior.
Examples, documentation, and release metadata
apps/storybook/src/MegaMenu.stories.tsx, apps/web/examples/megaMenu/*, apps/web/content/docs/components/mega-menu.mdx, .changeset/olive-donkeys-shave.md
Added multiple-dropdown examples, documentation, Storybook coverage, example exports, and patch release metadata.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant MegaMenuDropdownToggle
  participant MegaMenuDropdown
  participant MegaMenuContext
  User->>MegaMenuDropdownToggle: click toggle
  MegaMenuDropdownToggle->>MegaMenuDropdown: toggle paired panel
  MegaMenuDropdownToggle->>MegaMenuContext: set active toggle ID
  MegaMenuContext-->>MegaMenuDropdownToggle: notify other toggles
  MegaMenuDropdownToggle->>MegaMenuDropdown: hide previously open panel
Loading

Suggested labels: :bug: bug

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR meets the multiple-dropdown functionality requirements in [#1610] but does not address the issue's centered positioning and styling requirements. Implement reliable centered positioning and styling overrides for MegaMenuDropdown with toggle, or separate the positioning work into another linked issue.
Docstring Coverage ⚠️ Warning Docstring coverage is 45.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed The code, tests, documentation, and examples support the multiple-dropdown functionality described in [#1610] without unrelated changes.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: support for multiple dropdowns in one MegaMenu.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/ui/src/components/MegaMenu/MegaMenuDropdownToggle.tsx`:
- Around line 40-51: Update MegaMenuDropdownToggle’s onClick handler to stop
event propagation before toggling the paired dropdown, preventing the parent
NavbarLink from closing NavbarCollapse. Add an integration test covering
NavbarToggle, NavbarCollapse, and a nested MegaMenuDropdownToggle, verifying the
mobile navbar remains open when the dropdown is toggled.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 198cbcd9-1662-4b7c-aafa-bd94d9a40b14

📥 Commits

Reviewing files that changed from the base of the PR and between 85319bd and 8b31da1.

📒 Files selected for processing (12)
  • .changeset/olive-donkeys-shave.md
  • apps/storybook/src/MegaMenu.stories.tsx
  • apps/web/content/docs/components/mega-menu.mdx
  • apps/web/examples/megaMenu/index.ts
  • apps/web/examples/megaMenu/megaMenu.multipleDropdowns.tsx
  • packages/ui/src/components/MegaMenu/MegaMenu.test.tsx
  • packages/ui/src/components/MegaMenu/MegaMenu.tsx
  • packages/ui/src/components/MegaMenu/MegaMenuContext.tsx
  • packages/ui/src/components/MegaMenu/MegaMenuDropdown.tsx
  • packages/ui/src/components/MegaMenu/MegaMenuDropdownToggle.tsx
  • packages/ui/src/components/MegaMenu/helpers.ts
  • packages/ui/src/components/MegaMenu/index.ts

Comment thread packages/ui/src/components/MegaMenu/MegaMenuDropdownToggle.tsx
Children of MegaMenu are flex items of a "flex flex-wrap justify-between"
row, so a dropdown without a width class renders beside the navbar links
instead of below them, and the links shift whenever it is hidden.

Add w-full so each dropdown wraps onto its own row, and document it.

Refs themesberg#1610
@jorj-pineda

Copy link
Copy Markdown
Author

Demo

Recorded against the Storybook story added in this PR:
Company → Marketing → Company. Each toggle opens its own dropdown, and opening one closes the other.

2026-08-11.21-02-17.mp4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant