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

  1. Add a <mat-tab-nav-panel> element wrapping the tab content
  2. Bind [tabPanel] on <nav mat-tab-nav-bar> to the panel's template reference variable
  3. Ensure the template ref variable name matches exactly (e.g. #tabPanel)
  4. 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

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


AI-assisted analysis of angular/components@0411926e7d (2026-08-31). Data as JSON: /api/errors/d7f40c26607a09c1. Report an issue: GitHub.