Skip to content

fix(NavigationMenu): correct item-content and item-trailing slot behavior#6252

Open
TotomInc wants to merge 3 commits intonuxt:v4from
TotomInc:fix/navigation-menu-slots
Open

fix(NavigationMenu): correct item-content and item-trailing slot behavior#6252
TotomInc wants to merge 3 commits intonuxt:v4from
TotomInc:fix/navigation-menu-slots

Conversation

@TotomInc
Copy link
Copy Markdown

🔗 Linked issue

#6251

❓ Type of change

  • 📖 Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

Gate dropdown shell on children only (horizontal)

Removed all uses of global !!slots[…'item-content'…] from:

  • Whether to render NavigationMenuTrigger vs a plain link
  • Whether to render NavigationMenuContent
  • Default chevron visibility and the linkTrailing wrapper conditions tied to that pattern

Horizontal mega-menu UI is now driven by item.children?.length. #item-content only customizes the panel when those children exist; it no longer forces a panel for every row.

Note: If anyone relied on #item-content without children to force a panel, they need a real children list (or a future explicit API) instead.

Full override for #item-trailing

Replaced “slot with fallback” with an explicit branch:

  • If the relevant trailing slot is present (item-trailing or `${item.slot}-trailing`), render only that <slot> (same scoped props as before).
  • Otherwise render the previous default block (UBadge + conditional UIcons).

Demo

CleanShot.2026-03-27.at.16.38.41.mp4

Code of the demo (similar to the repro of the linked issue):

<script setup lang="ts">
import type { NavigationMenuItem } from '@nuxt/ui'

const items = computed<NavigationMenuItem[]>(() => [
  {
    label: 'Platforms',
    icon: 'i-lucide-server',
    children: [
      { label: 'Claude Code', to: '/claude-code' },
      { label: 'Claude Search', to: '/claude-search' },
      { label: 'Claude Vision', to: '/claude-vision' },
      { label: 'Claude Translate', to: '/claude-translate' },
      { label: 'Claude Write', to: '/claude-write' },
      { label: 'Claude Edit', to: '/claude-edit' },
      { label: 'Claude Generate', to: '/claude-generate' },
      { label: 'Claude Summarize', to: '/claude-summarize' }
    ]
  },
  {
    label: 'Models',
    icon: 'i-lucide-brain'
  },
  {
    label: 'Tools',
    icon: 'i-lucide-wrench'
  }
])
</script>

<template>
  <div>
    <UNavigationMenu :items="items">
      <template #item-trailing="{ item }">
        <span v-if="item.children">V</span>
      </template>

      <template #item-content="{ item }">
        <span v-if="item.children">More info...</span>
      </template>
    </UNavigationMenu>
  </div>
</template>

📝 Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

@TotomInc TotomInc requested a review from benjamincanac as a code owner March 27, 2026 15:39
@github-actions github-actions bot added the v4 #4488 label Mar 27, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 27, 2026

📝 Walkthrough

Walkthrough

This PR updates the NavigationMenu component documentation and implementation to clarify slot behavior. Documentation callouts are added explaining that the #item-trailing slot replaces default trailing UI (badge and chevron) and requires slot props for per-row variation, and that the #item-content slot in horizontal orientation only renders for items with a children array. The component logic is adjusted to remove dependency on item-content slot presence when deciding to render menu triggers and content in horizontal orientation, instead only checking for actual item.children presence. Tests are added to verify that the global item-content slot does not cause items without children to be treated as triggers, and that the item-trailing slot properly overrides default trailing icons.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and clearly describes the main changes: fixing the behavior of two specific slots (item-content and item-trailing) in the NavigationMenu component.
Description check ✅ Passed The description is directly related to the changeset, providing clear context about the slot behavior fixes, the rationale, and a working demo showing the corrected behavior.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/runtime/components/NavigationMenu.vue (1)

369-391: Simplify redundant orientation check on line 389.

The condition (orientation === 'horizontal' && item.children?.length) || (orientation === 'vertical' && item.children?.length) is logically equivalent to item.children?.length since orientation can only be 'horizontal' or 'vertical'.

✨ Suggested simplification
-          <UIcon v-if="(orientation === 'horizontal' && item.children?.length) || (orientation === 'vertical' && item.children?.length)" :name="item.trailingIcon || trailingIcon || appConfig.ui.icons.chevronDown" data-slot="linkTrailingIcon" :class="ui.linkTrailingIcon({ class: [uiProp?.linkTrailingIcon, item.ui?.linkTrailingIcon], active })" />
+          <UIcon v-if="item.children?.length" :name="item.trailingIcon || trailingIcon || appConfig.ui.icons.chevronDown" data-slot="linkTrailingIcon" :class="ui.linkTrailingIcon({ class: [uiProp?.linkTrailingIcon, item.ui?.linkTrailingIcon], active })" />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/NavigationMenu.vue` around lines 369 - 391, Replace
the redundant orientation checks that gate child presence with a simple check
for item.children?.length: update the v-if on the element with
data-slot="linkTrailing" and the UIcon v-if that currently uses "(orientation
=== 'horizontal' && item.children?.length) || (orientation === 'vertical' &&
item.children?.length)" to just "item.children?.length", leaving other logic
(badges, trailingIcon branch, class bindings, and onLinkTrailingClick)
unchanged; ensure both places (the wrapper v-if and the UIcon v-if) are updated
to the simplified condition.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/runtime/components/NavigationMenu.vue`:
- Around line 369-391: Replace the redundant orientation checks that gate child
presence with a simple check for item.children?.length: update the v-if on the
element with data-slot="linkTrailing" and the UIcon v-if that currently uses
"(orientation === 'horizontal' && item.children?.length) || (orientation ===
'vertical' && item.children?.length)" to just "item.children?.length", leaving
other logic (badges, trailingIcon branch, class bindings, and
onLinkTrailingClick) unchanged; ensure both places (the wrapper v-if and the
UIcon v-if) are updated to the simplified condition.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7c1ae9d2-4bdc-4375-9e93-70b4fd75a0f2

📥 Commits

Reviewing files that changed from the base of the PR and between 90a94fb and b73ce44.

📒 Files selected for processing (3)
  • docs/content/docs/2.components/navigation-menu.md
  • src/runtime/components/NavigationMenu.vue
  • test/components/NavigationMenu.spec.ts

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Mar 27, 2026

npm i https://pkg.pr.new/@nuxt/ui@6252

commit: 2cd47e8

@TotomInc
Copy link
Copy Markdown
Author

TotomInc commented Apr 8, 2026

Could we get a review please?

I've been using the generated package of this MR, for a website, and it seems to be working well.

Since then, we're stuck on the package because it isn't part of the merged releases of Nuxt UI.

Thanks!

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

Labels

v4 #4488

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant