angular/components · error · Error
A mat-tab-nav-panel must be specified via [tabPanel].
Error message
A mat-tab-nav-panel must be specified via [tabPanel].
What it means
MatTabNav (the nav-bar variant of tabs) requires its associated MatTabNavPanel so it can compute and position the ink bar over the correct panel region. The component validates this in ngAfterViewInit: if the `[tabPanel]` input was never bound, it throws in dev mode instead of silently rendering a broken ink bar.
Source
Thrown at src/material/tabs/tab-nav-bar/tab-nav-bar.ts:199
override ngAfterContentInit() {
this._inkBar = new MatInkBar(this._items);
// We need this to run before the `changes` subscription in parent to ensure that the
// selectedIndex is up-to-date by the time the super class starts looking for it.
this._items.changes
.pipe(startWith(null), takeUntil(this._destroyed))
.subscribe(() => this.updateActiveLink());
super.ngAfterContentInit();
// Turn the `change` stream into a signal to try and avoid "changed after checked" errors.
this._keyManager!.change.pipe(startWith(null), takeUntil(this._destroyed)).subscribe(() =>
this._focusedItem.set(this._keyManager?.activeItem || null),
);
}
override ngAfterViewInit() {
if (!this.tabPanel && (typeof ngDevMode === 'undefined' || ngDevMode)) {
throw new Error('A mat-tab-nav-panel must be specified via [tabPanel].');
}
super.ngAfterViewInit();
}
/** Notifies the component that the active link has been changed. */
updateActiveLink() {
if (!this._items) {
return;
}
const items = this._items.toArray();
for (let i = 0; i < items.length; i++) {
if (items[i].active) {
this.selectedIndex = i;
if (this.tabPanel) {
this.tabPanel._activeTabId = items[i].id;
}View on GitHub (pinned to 0411926e7d)
Solutions
- Add a <mat-tab-nav-panel> element wrapping the tab content
- Bind [tabPanel] on <nav mat-tab-nav-bar> to the panel's template reference variable
- Ensure the template ref variable name matches exactly (e.g. #tabPanel)
- Verify the MatTabNavPanel import from @angular/material/tabs is used, not a plain div
Example fix
// before <nav mat-tab-nav-bar> <a mat-tab-link ...>Link</a> </nav> <router-outlet></router-outlet> // after <nav mat-tab-nav-bar [tabPanel]="tabPanel"> <a mat-tab-link ...>Link</a> </nav> <mat-tab-nav-panel #tabPanel> <router-outlet></router-outlet> </mat-tab-nav-panel>
Defensive patterns
Strategy: validation
Validate before calling
// In the component/test, verify wiring before render
@ViewChild('tabPanel') tabPanel?: MatTabNavPanel;
assert(this.tabPanel != null, 'tabPanel must be bound to <mat-tab-nav-panel>'); Type guard
function hasTabPanel(nav: {tabPanel?: MatTabNavPanel}): nav is {tabPanel: MatTabNavPanel} {
return nav.tabPanel != null;
} Try / catch
try {
this.nav.ngAfterViewInit();
} catch (e) {
if (String(e?.message).includes('mat-tab-nav-panel')) {
console.error('Bind [tabPanel] to a <mat-tab-nav-panel #ref> element.');
}
} Prevention
- Always pair <nav mat-tab-nav-bar> with a <mat-tab-nav-panel> and [tabPanel] binding
- Name the template ref clearly (e.g. #tabPanel) and bind it in the same template
- Add a unit test that instantiates the nav bar and asserts no throw
- Review migration notes when moving from older MatTabNav APIs
When it happens
Trigger: Rendering `<nav mat-tab-nav-bar>` without `[tabPanel]="panelRef"` where panelRef is a template ref to a `<mat-tab-nav-panel>`.
Common situations: Migrating from the old MatTabNav API (which used a different panel wiring) to the current one; forgetting the <mat-tab-nav-panel> wrapper element when converting mat-tab-group to nav-bar style tabs; typos in the template ref variable name.
Related errors
- mat-tab-group background color must be set through the Sass
- No selected tab could be found.
- No active link could be found.
- Cannot specify both the `options` and `interval` inputs at t
- Value of `options` input cannot be an empty array
AI-assisted analysis of angular/components@0411926e7d (2026-08-31).
Data as JSON: /api/errors/d7f40c26607a09c1.
Report an issue: GitHub.