Skip to content

Navigation

Navigation bar

The bar at the bottom of compact windows to switch between three to five destinations.

Examples

Short navigation bar

Vertical items (the default) and horizontal items, which suit medium windows. Destinations with href are links; the selected one is aria-current="page". Badges show a dot or a count.

The live demo runs with JavaScript. Its code is below.

React HTML
import { type NavigationDestination, ShortNavigationBar } from '@kyralabs/m3exp-web/react';

const ITEMS: NavigationDestination[] = [
  { key: 'home', icon: 'home', label: 'Home' },
  { key: 'search', icon: 'search', label: 'Search' },
  { key: 'mail', icon: 'mail', label: 'Mail', badge: 3 },
  { key: 'settings', icon: 'settings', label: 'Settings', badge: true },
];

export default function Short() {
  return (
    <div style={{ display: 'grid', gap: 'var(--m3e-sys-space-200)', inlineSize: '100%' }}>
      <ShortNavigationBar aria-label="Main" items={ITEMS} defaultSelectedKey="home" />
      <ShortNavigationBar
        aria-label="Main, horizontal"
        layout="horizontal"
        items={ITEMS}
        defaultSelectedKey="mail"
      />
    </div>
  );
}
<div style="display: grid; gap: var(--m3e-sys-space-200); inline-size: 100%">
  <m3e-short-navigation-bar label="Main" selected="home">
    <m3e-navigation-item value="home" icon="home">Home</m3e-navigation-item>
    <m3e-navigation-item value="search" icon="search">Search</m3e-navigation-item>
    <m3e-navigation-item value="mail" icon="mail" badge="3">Mail</m3e-navigation-item>
    <m3e-navigation-item value="settings" icon="settings" badge="">Settings</m3e-navigation-item>
  </m3e-short-navigation-bar>
  <m3e-short-navigation-bar label="Main, horizontal" layout="horizontal" selected="mail">
    <m3e-navigation-item value="home" icon="home">Home</m3e-navigation-item>
    <m3e-navigation-item value="search" icon="search">Search</m3e-navigation-item>
    <m3e-navigation-item value="mail" icon="mail" badge="3">Mail</m3e-navigation-item>
    <m3e-navigation-item value="settings" icon="settings" badge="">Settings</m3e-navigation-item>
  </m3e-short-navigation-bar>
</div>

Run the HTML version (custom elements, no framework).

Navigation bar (deprecated)

Deprecated (D4): the 80dp NavigationBar ships until 2.0. Use the short navigation bar.

React HTML
import { type NavigationDestination, NavigationBar } from '@kyralabs/m3exp-web/react';

const ITEMS: NavigationDestination[] = [
  { key: 'home', icon: 'home', label: 'Home' },
  { key: 'search', icon: 'search', label: 'Search' },
  { key: 'settings', icon: 'settings', label: 'Settings' },
];

/** Deprecated (D4): use ShortNavigationBar. */
export default function LegacyNavigationBar() {
  return (
    <NavigationBar
      aria-label="Main, legacy"
      items={ITEMS}
      defaultSelectedKey="home"
      style={{ inlineSize: '100%' }}
    />
  );
}
<!-- Deprecated (D4). The CSS classes only lay the bar out: selection and focus are yours
     (m3e-react's <NavigationBar> handles them). -->
<nav class="m3e-navigation-bar" aria-label="Main, legacy" style="inline-size: 100%">
  <button type="button" class="m3e-navigation-item" aria-current="page">
    <span class="m3e-navigation-item__content">
      <span class="m3e-navigation-item__indicator" aria-hidden="true"></span>
      <span class="m3e-navigation-item__state" aria-hidden="true"></span>
      <span class="m3e-icon m3e-navigation-item__icon" aria-hidden="true">home</span>
      <span class="m3e-navigation-item__label">Home</span>
    </span>
  </button>
  <button type="button" class="m3e-navigation-item">
    <span class="m3e-navigation-item__content">
      <span class="m3e-navigation-item__indicator" aria-hidden="true"></span>
      <span class="m3e-navigation-item__state" aria-hidden="true"></span>
      <span class="m3e-icon m3e-navigation-item__icon" aria-hidden="true">search</span>
      <span class="m3e-navigation-item__label">Search</span>
    </span>
  </button>
  <button type="button" class="m3e-navigation-item">
    <span class="m3e-navigation-item__content">
      <span class="m3e-navigation-item__indicator" aria-hidden="true"></span>
      <span class="m3e-navigation-item__state" aria-hidden="true"></span>
      <span class="m3e-icon m3e-navigation-item__icon" aria-hidden="true">settings</span>
      <span class="m3e-navigation-item__label">Settings</span>
    </span>
  </button>
</nav>

Run the HTML version (CSS classes only).

API

React

<ShortNavigationBar>

Short navigation bar (COMPONENT_SPEC §13; Compose ShortNavigationBar): a <nav> of destinations, the selected one marked aria-current="page". Give it an aria-label.

Prop Type Default Description
layout 'vertical' | 'horizontal' vertical (default): icons above labels, equal-width items. horizontal: icons before labels, items centered (the medium-width configuration).
selectedKey string | null | undefined The selected destination (controlled). Exactly one item gets aria-current="page".
defaultSelectedKey string | null | undefined The initially selected destination (uncontrolled).
items required readonly NavigationDestination[]
onSelectionChange ((key: string) => void) | undefined Called when an item is pressed.

Also accepts HTML attributes not listed here.

<NavigationBar>

Deprecated. D4: use ShortNavigationBar. Shipped until 2.0; no new features.

Legacy navigation bar (Compose NavigationBar): 80dp tall, labels always shown.

Prop Type Default Description
selectedKey string | null | undefined The selected destination (controlled). Exactly one item gets aria-current="page".
defaultSelectedKey string | null | undefined The initially selected destination (uncontrolled).
items required readonly NavigationDestination[]
onSelectionChange ((key: string) => void) | undefined Called when an item is pressed.

Also accepts HTML attributes not listed here.

Custom elements

<m3e-short-navigation-bar>

import '@kyralabs/m3e-elements/short-navigation-bar.js';

<m3e-short-navigation-bar label="Main" selected="home"> (COMPONENT_SPEC §13; Compose ShortNavigationBar). Renders the m3e-css .m3e-short-navigation-bar contract inside itself (the host is display: contents): a <nav> of its <m3e-navigation-item> children, the DOM m3e-react's <ShortNavigationBar> renders. The indicator animation is CSS.

  • Attributes: label, selected (see M3eNavigationContainer), layout (vertical default | horizontal: icons before labels, items centered).
  • Events: selection-change (detail: { value }, cancelable).
Attribute Property Type Notes
label label string
selected selected string Reflected.
layout layout string
Event Detail Description
selection-change SelectionChangeDetail A destination was picked; cancelable: preventDefault() keeps the selection.

<m3e-navigation-item>

import '@kyralabs/m3e-elements/wide-navigation-rail.js';

<m3e-navigation-item value="home" icon="home" href="/">Home</m3e-navigation-item> — data for one destination of a <m3e-short-navigation-bar>, <m3e-wide-navigation-rail> or <m3e-modal-wide-navigation-rail> (it renders nothing itself; the container renders the .m3e-navigation-item contract): value (default: the label), icon (Material Symbols, filled while selected), href (renders a link; otherwise a button that only selects), disabled, label-hidden (icon only; the label becomes the accessible name), badge (a Compose Badge on the icon, as NavigationSuiteScaffold's NavigationItemIcon builds it: "" the dot, digits a count — 999+ above 999 — other text as is) and badge-label (the badge's visually hidden text, read after the label: default "New notification" for the dot, the shown text otherwise; "" leaves it out). The label is the text content. In a <m3e-navigation-drawer> (Compose NavigationDrawerItem) the icon is optional and badge is the trailing badge text instead (badge-label unused).

Attribute Property Type Notes
value value string
icon icon string
href href string
disabled disabled boolean
label-hidden labelHidden boolean
badge badge string
badge-label badgeLabel string

CSS

short-navigation-bar.css

@import '@kyralabs/m3e-css/short-navigation-bar.css';

Short navigation bar (COMPONENT_SPEC §13; Compose ShortNavigationBar). No JavaScript needed.

<nav class="m3e-short-navigation-bar" aria-label="Main">
  <a class="m3e-navigation-item" href="/" aria-current="page">…</a>   (navigation-item.css)
  …
</nav>

Vertical items (default): icon above the label, items share the width equally (ShortNavigationBarArrangement.EqualWeight). --horizontal: icon before the label and the items centered (NavigationItemIconPosition.Start + ShortNavigationBarArrangement.Centered, the medium-width configuration).

32 custom properties it reads
Custom property Default
--m3e-comp-divider-thickness 1px
--m3e-comp-minimum-interactive-component-size 48px
--m3e-comp-navigation-bar-container-color var(--m3e-sys-color-surface-container)
--m3e-comp-navigation-bar-container-height 64px
--m3e-comp-navigation-bar-horizontal-item-active-indicator-height 40px
--m3e-comp-navigation-bar-horizontal-item-icon-size 24px
--m3e-comp-navigation-bar-item-active-icon-color var(--m3e-sys-color-on-secondary-container)
--m3e-comp-navigation-bar-item-active-indicator-color var(--m3e-sys-color-secondary-container)
--m3e-comp-navigation-bar-item-active-label-text-color var(--m3e-sys-color-secondary)
--m3e-comp-navigation-bar-item-inactive-icon-color var(--m3e-sys-color-on-surface-variant)
--m3e-comp-navigation-bar-item-inactive-label-text-color var(--m3e-sys-color-on-surface-variant)
--m3e-comp-navigation-bar-label-text-font var(--m3e-sys-typescale-label-medium-font)
--m3e-comp-navigation-bar-label-text-line-height var(--m3e-sys-typescale-label-medium-line-height)
--m3e-comp-navigation-bar-label-text-size var(--m3e-sys-typescale-label-medium-size)
--m3e-comp-navigation-bar-label-text-tracking var(--m3e-sys-typescale-label-medium-tracking)
--m3e-comp-navigation-bar-label-text-weight var(--m3e-sys-typescale-label-medium-weight)
--m3e-comp-navigation-bar-vertical-item-active-indicator-height 32px
--m3e-comp-navigation-bar-vertical-item-active-indicator-width 56px
--m3e-comp-navigation-bar-vertical-item-icon-size 24px
--m3e-comp-short-navigation-bar-centered-item-max-width-1 100%
--m3e-comp-short-navigation-bar-centered-item-max-width-2 50%
--m3e-comp-short-navigation-bar-centered-item-max-width-3 33.3333%
--m3e-comp-short-navigation-bar-centered-item-max-width-4 25%
--m3e-comp-short-navigation-bar-centered-item-max-width-5 20%
--m3e-comp-short-navigation-bar-centered-item-max-width-6 16.6667%
--m3e-comp-short-navigation-bar-centered-item-min-width-1 40%
--m3e-comp-short-navigation-bar-centered-item-min-width-2 25%
--m3e-comp-short-navigation-bar-centered-item-min-width-3 20%
--m3e-comp-short-navigation-bar-centered-item-min-width-4 17.5%
--m3e-comp-short-navigation-bar-centered-item-min-width-5 16%
--m3e-comp-short-navigation-bar-centered-item-min-width-6 15%
--m3e-comp-short-navigation-bar-item-indicator-to-label-padding 4px

navigation-item.css

@import '@kyralabs/m3e-css/navigation-item.css';

Navigation item: the destination shared by the short navigation bar, the wide navigation rail and the legacy (D4) navigation bar / rail (COMPONENT_SPEC §13, §14). The container sets the geometry, colors and motion through --_m3e-nav-* properties; the item is never used alone.

<a class="m3e-navigation-item" href="/" aria-current="page">
  <span class="m3e-navigation-item__content">
    <span class="m3e-navigation-item__indicator" aria-hidden="true"></span>
    <span class="m3e-navigation-item__state" aria-hidden="true"></span>
    <span class="m3e-icon m3e-navigation-item__icon" aria-hidden="true">home</span>
    <span class="m3e-navigation-item__label">Home</span>
  </span>
</a>

With a badge (badge.css; Compose BadgedBox in the item's icon slot), the label comes first and the icon follows in a badged box, so the badge's hidden text is read after the label ("Inbox 8"; owner decision M9 #7). Without a label the item's aria-label carries both and the badge has no hidden label:

<span class="m3e-navigation-item__label">Inbox</span>
<span class="m3e-badged-box">
  <span class="m3e-icon m3e-navigation-item__icon" aria-hidden="true">inbox</span>
  <span class="m3e-badge m3e-badge--large">
    <span class="m3e-badge__text" aria-hidden="true">8</span>
    <span class="m3e-badge__label">8</span>
  </span>
</span>

Selected: aria-current (any value but "false") or data-selected. Disabled: aria-disabled="true". Layout: icon above the label; icon before the label inside a horizontal short navigation bar (Compose NavigationItemIconPosition.Start). The wide navigation rail lays its items out itself (wide-navigation-rail.css: one formula of the animated icon position). The badged box takes the icon's place in every layout. Badges are drawn as Compose places them: over the label in the horizontal bar (placeLabelAndStartIcon places the icon last), under it in the wide rail (placeAnimatedLabelAndIcon places the label last).

Badge bounds (Compose Modifier.badgeBounds() on NavigationItemLayout and AnimatedNavigationItemLayout, NavigationItem.kt L571 / L624; legacy NavigationBar.kt L536, NavigationRail.kt L542): the layout fills the item, so a badge never leaves the item. Each container sets --_m3e-badged-box-bounds-end / -top (badge.css) to the icon's distances from the item's end and top edges.

1 custom properties it reads
Custom property Default
--m3e-comp-minimum-interactive-component-size 48px

navigation-bar.css

@import '@kyralabs/m3e-css/navigation-bar.css';

@deprecated (D4) Legacy navigation bar (COMPONENT_SPEC §13; Compose NavigationBar). Use the short navigation bar (short-navigation-bar.css). Shipped until 2.0; no new features.

<nav class="m3e-navigation-bar" aria-label="Main">
  <a class="m3e-navigation-item" href="/" aria-current="page">…</a>   (navigation-item.css)
  …
</nav>
18 custom properties it reads
Custom property Default
--m3e-comp-divider-thickness 1px
--m3e-comp-navigation-bar-container-color var(--m3e-sys-color-surface-container)
--m3e-comp-navigation-bar-indicator-to-label-padding 4px
--m3e-comp-navigation-bar-item-active-icon-color var(--m3e-sys-color-on-secondary-container)
--m3e-comp-navigation-bar-item-active-indicator-color var(--m3e-sys-color-secondary-container)
--m3e-comp-navigation-bar-item-active-label-text-color var(--m3e-sys-color-secondary)
--m3e-comp-navigation-bar-item-horizontal-padding 8px
--m3e-comp-navigation-bar-item-inactive-icon-color var(--m3e-sys-color-on-surface-variant)
--m3e-comp-navigation-bar-item-inactive-label-text-color var(--m3e-sys-color-on-surface-variant)
--m3e-comp-navigation-bar-label-text-font var(--m3e-sys-typescale-label-medium-font)
--m3e-comp-navigation-bar-label-text-line-height var(--m3e-sys-typescale-label-medium-line-height)
--m3e-comp-navigation-bar-label-text-size var(--m3e-sys-typescale-label-medium-size)
--m3e-comp-navigation-bar-label-text-tracking var(--m3e-sys-typescale-label-medium-tracking)
--m3e-comp-navigation-bar-label-text-weight var(--m3e-sys-typescale-label-medium-weight)
--m3e-comp-navigation-bar-tall-container-height 80px
--m3e-comp-navigation-bar-vertical-item-active-indicator-height 32px
--m3e-comp-navigation-bar-vertical-item-active-indicator-width 56px
--m3e-comp-navigation-bar-vertical-item-icon-size 24px

Accessibility

<nav aria-label>; links with aria-current="page".

Every interactive part has a target of at least 48 × 48 px, a visible focus ring for keyboard focus only, and works under forced-colors and prefers-reduced-motion.

Sources

Values come from androidx Compose Material3 1.5.0-beta01 at 19991f2c and from m3.material.io, as cited below from COMPONENT_SPEC §13 and the m3e-css sources.

ShortNavigationBar reads NavigationBarTokens, NavigationBarVerticalItemTokens and NavigationBarHorizontalItemTokens (there is no ShortNavigationBar*Tokens.kt at 19991f2c); layout literals (TopIconIndicatorToLabelPadding, the Centered arrangement padding) generated into components/navigation-defaults.css from ShortNavigationBar.kt. Container surface-container, 64dp; indicator secondary-container 56×32 (horizontal: 40dp tall, hugging icon + label).

short-navigation-bar.css sources

(compose/material3 @ 19991f2c):

  • ShortNavigationBar.kt: container NavigationBarTokens.ContainerColor, content contentColorFor (onSurface), min height NavigationBarTokens.ContainerHeight, no elevation; window insets horizontal + bottom (here the safe-area insets).
  • ShortNavigationBarItem: indicator NavigationBarVerticalItemTokens.ActiveIndicatorWidth × ActiveIndicatorHeight, item padding ContainerBetweenSpace, indicator → label TopIconIndicatorToLabelPadding; start icon: indicator height NavigationBarHorizontalItemTokens.ActiveIndicatorHeight, leading space ActiveIndicatorLeadingSpace, icon → label NavigationBarTokens.ItemActiveIndicatorIconLabelSpace.
  • Colors: ShortNavigationBarItemDefaults (selected label: ItemActiveLabelTextColor with the icon on top, ItemActiveIconColor with the icon at the start). No color animation (animateColor = false); the indicator springs on DefaultSpatial.
  • EqualWeight: width / count per item. Centered: calculateCenteredContentHorizontalPadding per side (navigation-defaults.css, 1–6 items; 0 above), item width between (width − 2 × padding) / count and width / count, the row kept centered.
navigation-item.css sources

(compose/material3 @ 19991f2c):

  • NavigationItem.kt (ShortNavigationBarItem, WideNavigationRailItem): the whole item is the target, but the ripple, hover/focus overlay and focus ring sit on the indicator pill (IndicatorRipple, ripple(focusRingShape = indicatorShape), MappedInteractionSource). The ripple color is the container's content color (LocalContentColor = onSurface).
  • The indicator grows from the center: width = full width × progress, alpha = progress, both from one animateFloatAsState (animateIndicatorProgressAsState: DefaultSpatial). The legacy NavigationBarItem / NavigationRailItem animate width on FastSpatial and alpha on DefaultEffects.
  • Top-icon layout: indicator pill above the label (TopIconOrIconOnlyMeasurePolicy); without a label the pill alone is centered. Start-icon layout: the pill hugs icon + label (StartIconMeasurePolicy, AnimatedMeasurePolicy).
  • Icon and label colors: NavigationItemColors (selected / unselected / disabled = unselected at DisabledAlpha). Selected icons switch to FILL 1 (COMPONENT_SPEC §0, §13).
navigation-bar.css sources

(compose/material3 @ 19991f2c):

  • NavigationBar.kt: container NavigationBarTokens.ContainerColor, content contentColorFor (onSurface), NavigationBarDefaults.Elevation (Level0, no tonal overlay), min height NavigationBarTokens.TallContainerHeight, items spaced NavigationBarItemHorizontalPadding and sharing the width (weight 1); window insets horizontal + bottom (here the safe-area insets).
  • NavigationBarItem: indicator NavigationBarVerticalItemTokens.ActiveIndicatorWidth × ActiveIndicatorHeight, label NavigationBarIndicatorToLabelPadding below; the content is centered with the indicator's vertical padding counted once (placeLabelAndIcon), i.e. centered above a bottom inset of that padding. Icon and label colors animate on DefaultEffects; the indicator width on FastSpatial, its alpha on DefaultEffects. Colors: NavigationBarItemDefaults (NavigationBarTokens; selected label ItemActiveLabelTextColor).