Expressive tab bar
Rounded Material 3 expressive tabs for section navigation, built on Material Web tabs.
import 'material-next-web-components/expressive-tab-bar.js';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.
<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.
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
ariaLabel | aria-label | string | 'Article tabs' | Accessible label for the navigation landmark. |
activeIndex | active-index | number | 0 | Index of the active tab. |
iconPosition | icon-position | ExpressiveTabIconPosition | 'start' | Places tab icons before, after, above, or below tab labels. |
tabs | Property only | ExpressiveTabItem[] | exampleNavigationItems | The tabs, as {label, href, icon?} objects. JavaScript property only. The
default is three placeholder tabs linking to # anchors; always set it. |
| Event | Description |
|---|---|
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. |
| Custom property | Description |
|---|---|
--mnw-expressive-tab-bar-container-color | Bar background. |
--mnw-expressive-tab-bar-on-container | Inactive label and icon colour. |
--mnw-expressive-tab-bar-active-container | Active tab background. |
--mnw-expressive-tab-bar-on-active-container | Active label and icon colour. |
--mnw-expressive-tab-bar-shape | Corner radius. Default 999px. |
--mnw-expressive-tab-bar-height | Tab height. Default 80px. |
--mnw-expressive-tab-min-width | Minimum tab width. |
--mnw-expressive-tab-padding-inline | Tab inline padding. |
| Part | Description |
|---|---|
tabs | The underlying Material Web tabs element. |
tab | Each 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 byaria-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
hrefcontains 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.