Skip to main content

rc-navigation-bar

Bottom navigation layout that styles consumer-authored links.

Use this for compact PWA navigation where native anchors should remain router friendly and work before custom element upgrade. Wrap it in a native <nav> to provide navigation landmark semantics. The component deliberately provides no landmark role of its own because the app owns page structure and may render multiple navigation surfaces. Mark the current link with aria-current="page" or provide active-selector for router active classes.

Package
@rcarls/rc-navigation-bar
Element
<rc-navigation-bar>
Native dependency
Consumer-authored <a> links
State model
Current page comes from aria-current or active-selector
Main events
None

Installation

npm install @rcarls/rc-navigation-bar
import '@rcarls/rc-navigation-bar/define';

Usage

<nav aria-label="Main navigation">
<rc-navigation-bar>
<a href="/library" aria-current="page">
<span data-rc-navigation-icon aria-hidden="true">local_library</span>
<span>Library</span>
</a>
<a href="/saved">
<span data-rc-navigation-icon aria-hidden="true">bookmark</span>
<span>Saved</span>
</a>
<a href="/settings">
<span data-rc-navigation-icon aria-hidden="true">settings</span>
<span>Settings</span>
</a>
</rc-navigation-bar>
</nav>

The native <nav> is the navigation landmark and owns its accessible label, just as <search> owns landmark semantics around rc-search-bar. rc-navigation-bar only provides layout, active-state styling, and indicator positioning.

The active indicator measures [data-rc-navigation-indicator], then [data-rc-navigation-icon], then the active link itself. Use indicator-target when your markup needs a different measured element. The bundled themes set the tagged icon target's line-height equal to its block-size, keeping icon-font glyphs centered within the same box used for hover and indicator geometry. Native <img>, <svg>, and Iconify icons use that same target geometry without requiring a separate font offset. The higher-order --rc-icon-font-size and --rc-icon-font-line-height theme tokens also apply to button, rail, and generic [data-rc-icon] markup. Override either token at the theme root or on one component; for example, set --rc-icon-font-line-height: 1 to restore an icon font's em-based line height.

Live demo

Router active classes

<nav aria-label="Main navigation">
<rc-navigation-bar active-selector="a.router-active">
<a class="router-active" href="/library">Library</a>
<a href="/settings">Settings</a>
</rc-navigation-bar>
</nav>

Adaptive navigation

Keep breakpoint policy in app/layout code. The navigation components do not own media queries, route matching, or persistence; they only style the native links you render into them.

Use the Material window classes from your app shell:

Window widthApp-owned navigation surface
<600px compactrc-navigation-bar in the bottom app chrome
>=600px mediumcollapsed rc-navigation-rail in the leading app chrome
>=840px expandedrc-navigation-rail, optionally expandable/persisted

Render both surfaces from the same destination data and mark the current native link in exactly one place, either with aria-current="page" or with a router class matched by active-selector.

const destinations = [
{ href: '/', label: 'Home', icon: 'home' },
{ href: '/library', label: 'Library', icon: 'local_library' },
{ href: '/settings', label: 'Settings', icon: 'settings' },
];

const isMedium = matchMedia('(min-width: 600px)').matches;
const isExpanded = matchMedia('(min-width: 840px)').matches;
<!-- compact: <600px -->
<nav aria-label="Main navigation">
<rc-navigation-bar>
<a href="/library" aria-current="page">
<span data-rc-navigation-icon aria-hidden="true">local_library</span>
<span>Library</span>
</a>
<a href="/settings">
<span data-rc-navigation-icon aria-hidden="true">settings</span>
<span>Settings</span>
</a>
</rc-navigation-bar>
</nav>

<!-- medium and expanded: >=600px -->
<nav aria-label="Main navigation">
<rc-navigation-rail>
<a href="/library" aria-current="page">
<span data-rc-navigation-icon aria-hidden="true">local_library</span>
<span>Library</span>
</a>
<a href="/settings">
<span data-rc-navigation-icon aria-hidden="true">settings</span>
<span>Settings</span>
</a>
</rc-navigation-rail>
</nav>

At >=840px, let the app decide whether users may expand the rail and whether that preference is persisted:

const key = 'app.navigation-rail-expanded';
const rail = document.querySelector('rc-navigation-rail');

rail.toggleable = isExpanded;
rail.expanded = isExpanded && localStorage.getItem(key) === 'true';
rail.addEventListener('rc-navigation-rail-toggle', (event) => {
localStorage.setItem(key, String(event.detail.expanded));
});

When switching modes in an SPA, wrap the app-owned state update in document.startViewTransition() when available and assign stable transition names in CSS:

rc-navigation-bar,
rc-navigation-rail {
view-transition-name: app-navigation;
}

rc-navigation-bar::part(indicator),
rc-navigation-rail::part(indicator) {
view-transition-name: app-navigation-indicator;
}

For measured morphs across unrelated layout regions, use a FLIP/Web Animations helper in the app layer. That code needs page geometry and should stay outside the component core.

Material theme

rc-theme-material/components.css applies the Material 3 bottom navigation metrics used by the official Material Components implementation: an 80dp surface, 64×32dp active indicator target, centered 24dp icon, safe-area padding, label-medium typography, and state-layer hover treatment.

The Substrate theme highlights the active link itself and disables the moving indicator. The Material theme retains its separate animated indicator behind the icon target.

API

Properties

PropertyMarkupTypeDefaultDescription
activeSelectoractive-selectorstring'a[aria-current]:not([aria-current="false"])'Selector used to find the active link. Defaults to `aria-current`.
indicatorTargetindicator-targetstring'[data-rc-navigation-indicator], [data-rc-navigation-icon]'Selector inside the active link used for indicator geometry.

Methods

No public methods are documented in the custom elements manifest.

Events

No custom events are documented in the custom elements manifest.

Slots

NameDescription
defaultNavigation links. Direct `<a>` children are recommended.

CSS Custom Properties

PropertyDefaultDescription
--rc-navigation-bar-bgCanvasNavigation surface background.
--rc-navigation-bar-colorCanvasTextNavigation text color.
--rc-navigation-bar-block-size4remMinimum block size for the bar.
--rc-navigation-bar-padding-block0Bar block-axis padding.
--rc-navigation-bar-padding-inline0Bar inline-axis padding.
--rc-navigation-bar-gap0Gap between slotted items.
--rc-navigation-bar-item-gap0.25remGap between item icon and label.
--rc-navigation-bar-item-min-block-size3remMinimum item block size.
--rc-navigation-bar-item-padding-block0.5remItem block-axis padding.
--rc-navigation-bar-item-padding-inline0.75remItem inline-axis padding.
--rc-navigation-bar-item-colorinheritResting item text color.
--rc-navigation-bar-item-text-decorationNot specifiedSlotted link text decoration (defers to native anchor text-decoration when unset).
--rc-navigation-bar-active-colorNot specifiedActive item text color. Falls back to `--rc-navigation-bar-item-color`, then `inherit`.
--rc-navigation-bar-indicator-bgtransparentActive indicator background.
--rc-navigation-bar-indicator-border1px solid HighlightActive indicator border.
--rc-navigation-bar-indicator-radius0Active indicator corner radius.
--rc-navigation-bar-indicator-duration0msActive indicator transition duration.
--rc-navigation-bar-indicator-easingeaseActive indicator transition easing.
--rc-navigation-bar-focus-ringNot specifiedSlotted link focus outline (defers to native outline when unset).
--rc-navigation-bar-focus-ring-offset2pxSlotted link outline offset.

CSS Parts

PartDescription
navThe navigation layout container.
indicatorThe active item indicator.