Skip to content

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

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

jorj-pineda wants to merge 8 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:coverage && bun run build

Review preparation

  • Added regression coverage for separated toggle/panel pairs mixed with a standalone dropdown, including opening the standalone panel before switching paired panels.
  • Local coverage run: 49 test files passed; 343 tests passed, 1 skipped, 1 todo.
  • Full workspace build passed, including the UI package, CLI, docs site, and Storybook.
  • The existing inline review thread is already resolved; the reported nested NavbarLink behavior was withdrawn as pre-existing.

AI assistance

Codex (gpt-6.1-sol) inspected the review state, added the mixed-variant regression test, ran repository checks, and updated this description. Jorge remains the sole Git commit author.

Refs #1610

Summary by CodeRabbit

  • New Features
    • MegaMenu supports multiple dropdowns in one navigation bar, paired by declaration order even when elements aren’t siblings.
    • Opening a dropdown closes any other open dropdown.
    • Added an example showing multiple dropdowns with navigation links.
  • Documentation
    • Added guidance and examples for configuring multiple MegaMenu dropdowns.
  • Bug Fixes
    • Improved dropdown pairing, visibility, and accessibility state handling.
  • Tests
    • Added coverage for dropdown pairing, independent toggling, and automatic closing.

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: 5472b4e

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 in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 36cbac9e-232c-4b25-a71f-02677892815d
Commits

Reviewing files that changed from the base of the PR and between 6e5366b and 5472b4e.

Files selected for processing: 1
  • packages/ui/src/components/MegaMenu/MegaMenu.test.tsx

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

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.

Priority: Normal

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

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant MegaMenuDropdownToggle
  participant MegaMenuContext
  participant MegaMenuDropdownPanel
  User->>MegaMenuDropdownToggle: click toggle
  MegaMenuDropdownToggle->>MegaMenuDropdownPanel: toggle paired panel
  MegaMenuDropdownToggle->>MegaMenuContext: set active toggle ID
  MegaMenuContext-->>MegaMenuDropdownToggle: provide updated active toggle ID
  MegaMenuDropdownToggle->>MegaMenuDropdownPanel: hide panel when another toggle is active
Loading
Merge Risk: Low · up to 5472b

Dynamic menu changes can produce incorrect accessibility associations or unexpectedly hide a panel. These bounded cases do not block merging.

Pre-merge checks: Passed 4
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main change: adding support for multiple dropdowns in one MegaMenu.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@SutuSebastian SutuSebastian added the needs-changes Review requested changes - author action needed label Oct 28, 2026
@SutuSebastian

Copy link
Copy Markdown
Collaborator

Close one. Resolve the open threads and I'll review.

This branch has not been deployed

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

Labels

needs-changes Review requested changes - author action needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants