bottom navigation
Persistent destinations with visible labels, optional authored icons, and a quiet active fill. Native links; no enhancement module.
Example
Persistent destinations below a scrolling page. The icons and unread badge belong to this example; Mica does not supply an icon set.
Usage
Use a named native nav with direct links. Mark the current destination with aria-current="page". No script or custom element registration is needed.
<nav data-bottom-nav aria-label="Primary">
<a href="/home">Home</a>
<a href="/projects" aria-current="page">Projects</a>
<a href="/inbox">Inbox</a>
<a href="/settings">Settings</a>
</nav>
Links receive equal width. Text-only navigation uses a compact target height; adding data-bottom-icon opts the whole bar into taller icon-and-label sizing. Keep labels short and visible.
Optional icons and badges
<a href="/inbox" aria-label="Inbox, 3 unread">
<svg data-bottom-icon aria-hidden="true" viewBox="0 0 24 24">
<!-- Your icon paths -->
</svg>
<span>Inbox <span aria-hidden="true">3</span></span>
</a>
data-bottom-icon only sizes your icon. It does not inject artwork. Badge layout is authored content; include its meaning in the link’s accessible name. Use a consistent icon treatment across the bar.
Keeping the bar at the bottom
The component stays in normal flow. Give it the last row of a viewport-height shell, with a scrolling main region. This reserves its actual height, including safe-area padding and wrapped labels.
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<div class="app">
<header>…</header>
<main>…</main>
<nav data-bottom-nav aria-label="Primary">…</nav>
</div>
<style>
.app {
display: grid;
grid-template-rows: auto minmax(0, 1fr) auto;
block-size: 100svh;
block-size: 100dvh;
}
.app > main {
min-block-size: 0;
overflow: auto;
}
</style>
Mica adds bottom and side safe-area padding. The app chooses its responsive breakpoint and whether a header or sidebar replaces this navigation on larger screens. The bar does not hide itself, fix itself to the viewport, or manage the software keyboard.
States and behavior
- Current destination:
aria-current="page"gives a quiet background, stronger text, and no link border. - Unavailable destination: remove
hrefand addaria-disabled="true". The attribute alone does not stop a native link from navigating. - Keyboard: normal Tab/Shift+Tab and Enter, with the shared focus ring. These are navigation links, not tabs; do not add tab roles or arrow-key behavior.
- Navigation and focus after a route change belong to the browser or your router. The example’s script demonstrates routing and is not part of the library.
API and tokens
| Part | Contract | Description |
|---|---|---|
data-bottom-nav | On a native nav | Direct children are native links. Supply a navigation label. |
data-bottom-icon | Optional, inside a link | Sizes an authored icon and selects icon-and-label spacing for the bar. |
| Token | Default | Description |
|---|---|---|
--bottom-nav-pad | --space-xs | Bar padding, increased to accommodate safe areas. |
--bottom-nav-gap | --space-2xs | Gap between links and between icon and label. |
--bottom-nav-target-height | 3.5rem / 2.75rem | Minimum link height with icons / text only. |
--bottom-nav-font-size | .75rem / .8125rem | Label size with icons / text only. |
--bottom-nav-font-weight | 500 | Default label weight. |
--bottom-nav-active-weight | 650 | Current destination weight. |
--bottom-nav-icon-size | 1.125rem | Authored icon width and height. |
--bottom-nav-active-background | --color-surface-raised | Current destination fill. Set to transparent for emphasis without fill. |
Colors and radius follow Mica’s semantic tokens. Custom properties set on the nav override the defaults, including automatic text-only sizing.