diff --git a/docs/content/docs/2.components/navigation-menu.md b/docs/content/docs/2.components/navigation-menu.md
index 8e7de5c6fd..9e07aad6ee 100644
--- a/docs/content/docs/2.components/navigation-menu.md
+++ b/docs/content/docs/2.components/navigation-menu.md
@@ -1340,6 +1340,10 @@ You can also use the `#item`, `#item-leading`, `#item-label`, `#item-trailing` a
Use the `#item-trailing` slot or the `slot` property (`#{{ item.slot }}-trailing`) to add a [DropdownMenu](/docs/components/dropdown-menu) that appears on hover, similar to Notion or Linear.
+::note
+When you pass `#item-trailing` (or `#{{ item.slot }}-trailing`), it replaces the default trailing UI for every item (badge and chevron). Use the slot props (`item`, `active`, etc.) to render different content per row.
+::
+
::component-example
---
collapse: true
@@ -1351,6 +1355,10 @@ name: 'navigation-menu-trailing-slot-example'
Use the `#item-content` slot or the `slot` property (`#{{ item.slot }}-content`) to customize the content of a specific item.
+::note
+In **horizontal** orientation, the menu trigger and panel are only rendered for items that define a `children` array. The `#item-content` slot customizes what appears inside that panel, it does not create a dropdown for items without `children`.
+::
+
::component-example
---
collapse: true
diff --git a/src/runtime/components/NavigationMenu.vue b/src/runtime/components/NavigationMenu.vue
index 877fbd2e79..f238bc05b6 100644
--- a/src/runtime/components/NavigationMenu.vue
+++ b/src/runtime/components/NavigationMenu.vue
@@ -366,13 +366,16 @@ function onLinkTrailingClick(e: Event, item: NavigationMenuItem) {
onLinkTrailingClick(e, item)"
>
-
+
+
+
+
-
+
-
+
@@ -402,7 +405,7 @@ function onLinkTrailingClick(e: Event, item: NavigationMenuItem) {
-
+
-
diff --git a/test/components/NavigationMenu.spec.ts b/test/components/NavigationMenu.spec.ts
index 99351ba9a0..907053598d 100644
--- a/test/components/NavigationMenu.spec.ts
+++ b/test/components/NavigationMenu.spec.ts
@@ -1,4 +1,5 @@
import { describe, it, expect, test } from 'vitest'
+import { h } from 'vue'
import { axe } from 'vitest-axe'
import { mountSuspended } from '@nuxt/test-utils/runtime'
import { renderEach } from '../component-render'
@@ -130,6 +131,46 @@ describe('NavigationMenu', () => {
expect(await axe(wrapper.element)).toHaveNoViolations()
})
+ it('does not treat global item-content slot as a trigger for items without children', async () => {
+ const wrapper = await mountSuspended(NavigationMenu, {
+ props: {
+ orientation: 'horizontal',
+ variant: 'link',
+ items: [[
+ { label: 'NoChildren', to: '/' },
+ { label: 'WithChildren', to: '/', children: [{ label: 'Child', to: '/child' }] }
+ ]]
+ },
+ slots: {
+ 'item-content': ({ item }: { item: { children?: unknown[] } }) => h('div', item.children?.length ? 'custom' : '')
+ }
+ })
+
+ // Only the item with `children` uses NavigationMenuTrigger; the slot must not force a trigger on plain links.
+ expect(wrapper.findAll('[data-navigation-menu-trigger]')).toHaveLength(1)
+ })
+
+ it('item-trailing slot fully overrides default trailing icons', async () => {
+ const wrapper = await mountSuspended(NavigationMenu, {
+ props: {
+ orientation: 'horizontal',
+ variant: 'link',
+ items: [[
+ { label: 'NoChildren', to: '/' },
+ { label: 'WithChildren', to: '/', children: [{ label: 'Child', to: '/child' }] }
+ ]]
+ },
+ slots: {
+ 'item-trailing': ({ item }: { item: { children?: unknown[] } }) => item.children?.length
+ ? h('span', { 'data-testid': 'custom-trailing' }, 'V')
+ : undefined
+ }
+ })
+
+ expect(wrapper.findAll('[data-slot="linkTrailingIcon"]')).toHaveLength(0)
+ expect(wrapper.findAll('[data-testid="custom-trailing"]')).toHaveLength(1)
+ })
+
test('should have the correct types', () => {
// normal
expectSlotProps('item', () => NavigationMenu({