nuxt / nuxt/ui

NavigationMenuItem's `ui` type requires every slot key instead of allowing partial overrides

Open Beginner friendly
#6,974 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
6.9k
Forks
1.1k
Avg merge
1d 7h
Merged PRs (30d)
57

Description

Environment
  • Package Manager: npm
  • @nuxt/ui: 4.11.1
  • vue: 3.5.43
  • Vue + Inertia.js (no Nuxt), using @nuxt/ui/vite with router: "inertia"
  • Type-checked with vue-tsc --noEmit
Is this bug related to Nuxt or Vue?

Vue

Package

v4.x

Version

v4.11.1

Reproduction

No external sandbox — this is a pure type-definition issue, fully reproducible from the shipped .d.ts alone:

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

const items = computed<NavigationMenuItem[]>(() => [
  {
    label: 'Feed',
    to: '/feed',
    active: true,
    // Type error: missing 'label', 'link', 'content', 'item', and 18 more
    ui: { linkLeadingIcon: 'text-court-950' },
  },
])
</script>
Description

NavigationMenuItem['ui'] is typed as a Pick over the component's generated slots type:

// node_modules/@nuxt/ui/dist/runtime/components/NavigationMenu.vue.d.ts
ui?: Pick<NavigationMenu['slots'], 'item' | 'linkLeadingAvatarSize' | 'linkLeadingAvatar' | 'linkLeadingIcon' | 'linkLeadingChipSize' | 'linkLabel' | 'linkLabelExternalIcon' | 'linkTrailing' | 'linkTrailingBadgeSize' | 'linkTrailingBadge' | 'linkTrailingIcon' | 'label' | 'link' | 'content' | 'childList' | 'childLabel' | 'childItem' | 'childLink' | 'childLinkIcon' | 'childLinkWrapper' | 'childLinkLabel' | 'childLinkLabelExternalIcon' | 'childLinkDescription'>;

NavigationMenu['slots'] resolves to an index-signature type ({ [x: string]: SlotClass }), not an interface with individually optional keys. Picking specific literal keys off an index-signature type produces an object type where every picked key is required, since index signatures carry no per-property optionality for Pick to preserve.

The result: although ui itself is optional on NavigationMenuItem, supplying any ui object requires values for all ~21 picked slot keys — even though at runtime tv() merges partial slot overrides just fine. Overriding a single slot like linkLeadingIcon per-item (a use case asked about in #3683) is a TypeScript error.

Expected behavior: overriding a single slot on an item's ui prop should type-check, matching the runtime merge behavior — e.g. by wrapping the Pick result in Partial<...>:

ui?: Partial<Pick<NavigationMenu['slots'], /* ... */>>;

Current workaround: cast the object to the field's type to bypass the over-strict check:

ui: { linkLeadingIcon: 'text-court-950' } as NavigationMenuItem['ui'],
Additional context

The same Pick-based pattern (and likely the same issue) appears on other components' per-item ui fields, e.g. NavigationMenuChildItem.

Logs
error TS2322: Type '{ label: string; to: string; active: true; ui: { linkLeadingIcon: string; }; }' is not assignable to type 'NavigationMenuItem'.
  Types of property 'ui' are incompatible.
    Type '{ linkLeadingIcon: string; }' is missing the following properties from type
    'Pick<{ [x: string]: SlotClass; }, "label" | "link" | "linkLeadingIcon" | "content" | "item" | ... 14 more ... | "childLinkDescription">':
    label, link, content, item, and 18 more.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with node_modules/@nuxt/ui/dist/runtime/components/NavigationMenu.vue.d.ts and reproduce the issue using vue-tsc --noEmit with the provided NavigationMenuItem example. Check the related NavigationMenuChildItem declaration as well. Done means a single-slot ui override type-checks without a cast, while the existing NavigationMenuItem fields remain valid.

Written by the indexing model from the issue text.

Assessment

Tech stack
nuxt, typescript
Domain
frontend
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.