Skip to content

fix(breadcrumb): expose aria-current on the current page item - #1402

Open
dmorfav wants to merge 1 commit into
openng-org:mainfrom
dmorfav:fix/breadcrumb-aria-current
Open

fix(breadcrumb): expose aria-current on the current page item#1402
dmorfav wants to merge 1 commit into
openng-org:mainfrom
dmorfav:fix/breadcrumb-aria-current

Conversation

@dmorfav

@dmorfav dmorfav commented Jul 25, 2026

Copy link
Copy Markdown

Fixes #54

p-breadcrumb never exposed the current page to assistive technology, even though the component's own Accessibility docs already promise it:

If the last link represents the current route, aria-current is added with "page" as the value.

The current item now actually does that, as required by the APG breadcrumb pattern.

Two link branches, two mechanisms

The template renders items through two different anchors, and they need different handling:

  • url items[attr.aria-current], following the convention already used by galleria, carousel, steps, paginator and stepper.
  • routerLink items[ariaCurrentWhenActive], the RouterLinkActive input. An attribute binding does not survive here: RouterLinkActive.update() calls removeAttribute('aria-current') on every update, inside a queueMicrotask, so it always runs after the binding and wipes it.

That second point confirms the report in the issue — the passthrough on itemLink really was being overwritten. The cause is RouterLinkActive, not routerLink itself.

This also means steps.ts:55's [attr.ariaCurrentWhenActive] is a no-op: with the attr. prefix it renders a literal attribute instead of binding the directive input. Left untouched here, but worth a separate look.

Behaviour

Items hidden with visible: false are skipped, so the attribute lands on the item the user actually sees instead of on an <li> that is never rendered.

Note the resulting asymmetry: url items always mark the last visible one, while routerLink items are only marked when the router considers them active. In a real breadcrumb both coincide, since the last item is where you are. I kept the router-driven behaviour rather than forcing the attribute, because claiming aria-current="page" on a link pointing somewhere else would be worse than not setting it. Happy to change it if you'd rather have it unconditional.

Scope

Only items in model. The home link lives in a separate branch of the template and is left untouched, so with an empty model nothing is marked. Tell me if you want home to be current when it is the only entry.

Tests

Four cases in the existing accessibility block of breadcrumb.spec.ts, asserting on the rendered DOM rather than on component state: the last url item is marked and the preceding ones carry no aria-current at all; hidden trailing items are skipped; a routerLink item is marked once its route is active, and not before. Full breadcrumb suite: 89 passing.

Beyond Karma, I went through the docs app and checked in the rendered DOM: no regression on the Template demo (which renders no anchors of its own), routerLinkActive still applies its CSS class, no new console warnings, the attribute follows the last item when model is replaced, and empty / single-item / all-hidden / undefined models all behave without throwing.

VoiceOver on Safari announces the last item as "Wireless, current page, list item", which is the behaviour the issue asks for.

image

VoiceOver on Safari announces the last item as the current page, which is the behaviour the issue asks for. Apologies for the Spanish locale in the screenshot below — "Wireless, página actual, ítem de lista" reads "Wireless, current page, list item".

I also ran the Lighthouse/axe audit on /breadcrumb from both main and this branch: same score (80), same 8 failures, identical rule-and-selector list — no new violation. aria-valid-attr-value, aria-allowed-attr and aria-conditional-attr are all evaluated against the rendered aria-current and pass.

One pre-existing violation worth flagging, untouched here: axe reports link-name on the home anchor, because the demo declares it as { icon: 'pi pi-home' } with no label and no homeAriaLabel, leaving the link with no accessible name. Present on main too. Happy to open a separate issue for it.

One thing worth knowing: passthrough still cannot drive this attribute, on either branch. pt: { itemLink: { 'aria-current': 'step' } } is overridden — by the binding on url items, and by RouterLinkActive on routerLink ones. Not a regression (it never worked, which is what the issue reported), but it does mean values other than page aren't reachable. Say the word if you'd like that configurable.

Also spotted while testing, unrelated and left alone: with model = [] and a home set, the separator at breadcrumb.ts:83 still renders — *ngIf="model && home", and an empty array is truthy — leaving a trailing chevron with nothing after it. Happy to file it separately.

@geromegrignon geromegrignon added this to the Catch-up milestone Jul 26, 2026
The current item now sets aria-current="page", as required by the APG
breadcrumb pattern. Items hidden through visible: false are skipped so
the attribute lands on the item the user actually sees.

Items with routerLink go through the ariaCurrentWhenActive input rather
than an attribute binding: RouterLinkActive removes aria-current on
every update, after the binding has run.

Fixes openng-org#54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Accessibility | Breadcrumb - aria-current=page is not implemented

2 participants