Skip to content

bug(aria/menu): ngMenuItem value is required but unread, and the documented examples do not compile #33682

Description

@artaommahe

Is this a regression?

No.

Description

MenuItem.value is declared input.required(), but inside an ngMenu nothing reads it except a dev-mode duplicate check. The documented examples reflect that — several of them omit value — so they don't compile under strictTemplates.

The documented examples fail to compile. The JSDoc shipped on MenuBar and on MenuItem itself both show items without a value:

<div ngMenu #fileMenu="ngMenu">
  <div ngMenuItem>New</div>
  <div ngMenuItem>Open</div>
</div>

Compiling that with strictTemplates (@angular/aria 22.1.0):

error NG8008: Required input 'value' from directive MenuItem must be specified.

10       <div ngMenuItem>New</div>
                ~~~

The same shape appears in maintainer guidance — e.g. <div ngMenuItem (click)="markAsRead(item)"> in the discussion on #33303, described there as the designed usage.

Nothing in a menu reads the value. MenuPattern constructs its List with value: signal([]), so neither list selection nor list navigation ever consults an item's value. The two remaining reads are:

  1. MenuPattern.submit(), which passes item.value() to the panel-level itemSelected callback.
  2. MenuPattern.validate(), which warns Duplicate value '…' detected inside ngMenu under ngDevMode.

So for anyone who handles activation per item (a (click) on the item, as the docs show) rather than through itemSelected, the value is write-only — and it cannot be omitted, because every item would fall back to the same value and any panel with two or more items would report a duplicate-value violation.

The flag looks vestigial. ngMenuItem used to bind [attr.aria-label]="value()", which gave every item a required accessible name and a reason for the value to be mandatory. That binding was reported as a bug (#32893) and removed in #33202, but the required flag stayed.

Proposal

Make value optional on ngMenuItem:

  • value = input<V | undefined>(undefined) (or default it to the item's generated id, which already exists and is unique per item).
  • Skip the duplicate check for items with no value, so a panel of valueless items is valid.
  • itemSelected then emits undefined for such items, which is accurate: the consumer never named one.

Radio and checkbox items still want a meaningful value, and this doesn't change anything for them — it just stops requiring one from plain action items.

Why it can't be worked around in a wrapper

Wrapping MenuItem in hostDirectives doesn't help: Angular rejects a host directive that leaves a required input unexposed —

error NG2019: Required input 'value' from host directive MenuItem must be exposed.

— and hostDirectives only allows an alias, never a value. So a wrapper library has to keep the attribute in its own public API and reach into the input signal's node to fill it, which is what we ended up doing.

Reproduction

No StackBlitz: the reproduction is the five-line snippet above — @angular/aria's own documented example — compiled with strictTemplates: true.

Environment

  • Angular: 22.1.0, @angular/aria: 22.1.0
  • Verified with ngc and strictTemplates: true.

Filed by Claude, Anthropic's coding agent, on behalf of @artaommahe. The compile errors and source references above were verified locally against @angular/aria 22.1.0.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions