Material Next
On this page

Overview

<mnw-expressive-tab-bar> is a pill-shaped bar of Material Web primary tabs, with inline icons by default and one full-height selection pill that slides between tabs, matching the Material Design reference. Use it for a few peer sections of one page or article: Overview, Specs, Guidelines, Accessibility. It renders a <nav> landmark, and each tab with an href holds a real link.

Use plain Material Web md-tabs for panels that switch content in place without a URL, and a navigation bar or rail for an app's top-level destinations.

Live demo

Select a tab: change and navigate show here.

The bar cancels navigate, so the links do not move the page.
<mnw-expressive-tab-bar
  aria-label="Material guide sections"
></mnw-expressive-tab-bar>
<p class="demo-log" aria-live="polite">
  Select a tab: change and navigate show here.
</p>
import 'material-next-web-components/expressive-tab-bar.js';
import type {MaterialNextExpressiveTabBar} from 'material-next-web-components/expressive-tab-bar.js';

const tabs = [
  {label: 'Overview', icon: 'info', href: '#overview'},
  {label: 'Specs', icon: 'style', href: '#specs'},
  {label: 'Guidelines', icon: 'design_services', href: '#guidelines'},
  {label: 'Accessibility', icon: 'accessibility_new', href: '#accessibility'},
];

export default (root: HTMLElement) => {
  const log = root.querySelector('.demo-log')!;
  for (const bar of root.querySelectorAll<MaterialNextExpressiveTabBar>(
    'mnw-expressive-tab-bar'
  )) {
    // Objects go through the JavaScript property; there is no attribute for them.
    bar.tabs = tabs;
    bar.addEventListener('change', (event) => {
      const {activeIndex, item} = (event as CustomEvent).detail;
      log.textContent = `change: activeIndex ${activeIndex} (${item.label})`;
    });
    bar.addEventListener('navigate', (event) => {
      // Cancel to route in JavaScript instead of following the link.
      event.preventDefault();
      log.textContent += `\nnavigate: ${(event as CustomEvent).detail.item.href} (canceled)`;
    });
  }
};

Usage

Tabs are objects, so set them through the tabs property: {label: string, href: string, icon?: string}, where icon is a Material Symbols name. The default is three placeholder tabs, so always set it.

Selecting a tab updates activeIndex and fires change. If the tab has an href, navigate fires next: cancel it to route in JavaScript, or let the browser follow the link.

import 'material-next-web-components/expressive-tab-bar.js';

const bar = document.querySelector('mnw-expressive-tab-bar')!;
bar.tabs = [
  {label: 'Overview', icon: 'article', href: '/guide/'},
  {label: 'API', icon: 'code', href: '/guide/api/'},
];
bar.activeIndex = 0;
bar.addEventListener('navigate', (event) => {
  event.preventDefault();
  router.go((event as CustomEvent).detail.item.href);
});

Specs

The track and moving selection are fully rounded. The default height is 80px, icons are 24px, and icons and labels sit in a single row. The indicator fills the track's height. Narrow containers scroll horizontally instead of clipping a label. icon-position="top", bottom and end remain available when a product needs them.

API

A Material 3 expressive top app tab bar styled with rounded app-navigation chrome and Material Web tabs.

<mnw-expressive-tab-bar> properties
PropertyAttributeTypeDefaultDescription
ariaLabelaria-labelstring'Article tabs'Accessible label for the navigation landmark.
activeIndexactive-indexnumber0Index of the active tab.
iconPositionicon-positionExpressiveTabIconPosition'start'Places tab icons before, after, above, or below tab labels.
tabsProperty onlyExpressiveTabItem[]exampleNavigationItemsThe tabs, as {label, href, icon?} objects. JavaScript property only. The default is three placeholder tabs linking to # anchors; always set it.
<mnw-expressive-tab-bar> events
EventDescription
change{activeIndex, item} after the selected tab changes.
navigate{index, item} before following a tab's href. Cancelable: preventDefault() keeps the browser from following the link, for routing in JavaScript.
<mnw-expressive-tab-bar> CSS custom properties
Custom propertyDescription
--mnw-expressive-tab-bar-container-colorBar background.
--mnw-expressive-tab-bar-on-containerInactive label and icon colour.
--mnw-expressive-tab-bar-active-containerActive tab background.
--mnw-expressive-tab-bar-on-active-containerActive label and icon colour.
--mnw-expressive-tab-bar-shapeCorner radius. Default 999px.
--mnw-expressive-tab-bar-heightTab height. Default 80px.
--mnw-expressive-tab-min-widthMinimum tab width.
--mnw-expressive-tab-padding-inlineTab inline padding.
<mnw-expressive-tab-bar> CSS parts
PartDescription
tabsThe underlying Material Web tabs element.
tabEach Material Web primary tab.

icon-position accepts top, bottom, start and end; the default and any other value are treated as start. active-index is clamped to the tabs that exist. change only fires when the index actually changes.

Guidelines

Use the bar for peer sections such as Overview, Specs, Guidelines and Accessibility. Use the app shell for application destinations. Selecting a tab moves one shared pill; it does not draw a separate background on every tab. Reduced motion removes the pill's travel.

Accessibility

  • The bar is a <nav> named by aria-label (default "Article tabs"). Give each bar on a page its own name.
  • Material Web tabs move between tabs with the arrow keys. A tab with an href contains a link, so it can also be opened in a new tab.
  • Icons are decorative; the label is always shown.

The bar and selected tab are fully rounded by default (999px), including hover and pressed surfaces. Override --mnw-expressive-tab-bar-shape for a different shape.

Limitations

  • Experimental: names of the CSS custom properties may change before 1.0.
  • There are no tab panels: pair it with routing or anchors.
  • Many tabs scroll sideways; keep it to about five.
mail