Navigation bar
Three to five top level destinations along the bottom of a compact screen. On a wider screen use a navigation rail instead.
Usage
Mark the current destination with selected. That makes it the only tab stop in the
bar, so the whole bar is one stop in the tab order and the arrow keys move between destinations,
which is what a screen reader user expects of a navigation row.
<script lang="ts">
import { NavigationBar, NavigationBarItem } from 'noph-ui'
import { Icon } from 'noph-ui/icons'
const destinations = [
{ value: 'home', label: 'Home', icon: 'home' },
{ value: 'search', label: 'Search', icon: 'search' },
{ value: 'mail', label: 'Mail', icon: 'mail' },
{ value: 'settings', label: 'Settings', icon: 'settings' },
]
let selected = $state('home')
</script>
<div style="width:100%;max-width:26rem">
<NavigationBar aria-label="Main">
{#each destinations as destination (destination.value)}
<NavigationBarItem
label={destination.label}
selected={selected === destination.value}
onclick={() => (selected = destination.value)}
>
{#snippet icon()}<Icon>{destination.icon}</Icon>{/snippet}
</NavigationBarItem>
{/each}
</NavigationBar>
</div>
As links
Give an item an href and it renders an <a>. A selected item gets aria-current="page" either way.
<NavigationBarItem
label="Home"
href="/"
selected={page.url.pathname === '/'}
>
{#snippet icon()}<Icon>home</Icon>{/snippet}
</NavigationBarItem>Badges
badge puts a badge on the icon. Without
a badgeLabel it is a small dot. Give it a badgeAriaLabel so the count is announced, otherwise the badge is hidden from assistive
technology.
<script lang="ts">
import { NavigationBar, NavigationBarItem } from 'noph-ui'
import { Icon } from 'noph-ui/icons'
</script>
<div style="width:100%;max-width:26rem">
<NavigationBar aria-label="Main with badges">
<NavigationBarItem label="Home" selected>
{#snippet icon()}<Icon>home</Icon>{/snippet}
</NavigationBarItem>
<NavigationBarItem label="Mail" badge badgeLabel={3} badgeAriaLabel="3 unread">
{#snippet icon()}<Icon>mail</Icon>{/snippet}
</NavigationBarItem>
<NavigationBarItem label="Search" badge badgeAriaLabel="New results">
{#snippet icon()}<Icon>search</Icon>{/snippet}
</NavigationBarItem>
</NavigationBar>
</div>
Labels
labelBehavior="selected" shows only the selected item's label. The hidden labels keep their
space, so the row does not shift as the selection moves.
<script lang="ts">
import { NavigationBar, NavigationBarItem } from 'noph-ui'
import { Icon } from 'noph-ui/icons'
const destinations = [
{ value: 'home', label: 'Home', icon: 'home' },
{ value: 'search', label: 'Search', icon: 'search' },
{ value: 'mail', label: 'Mail', icon: 'mail' },
{ value: 'settings', label: 'Settings', icon: 'settings' },
]
let selectedCompact = $state('home')
</script>
<div style="width:100%;max-width:26rem">
<NavigationBar aria-label="Main, labels on selection">
{#each destinations as destination (destination.value)}
<NavigationBarItem
labelBehavior="selected"
label={destination.label}
selected={selectedCompact === destination.value}
onclick={() => (selectedCompact = destination.value)}
>
{#snippet icon()}<Icon>{destination.icon}</Icon>{/snippet}
</NavigationBarItem>
{/each}
</NavigationBar>
</div>
Arrangement
By default items share the bar equally (arrangement="equal-weight"), which M3
recommends for compact, phone-width screens. Once the bar has room to spare, switch to arrangement="centered" so each item keeps its own content width and the group sits in the
middle instead of stretching edge to edge.
<script lang="ts">
import { NavigationBar, NavigationBarItem } from 'noph-ui'
import { Icon } from 'noph-ui/icons'
const destinations = [
{ value: 'home', label: 'Home', icon: 'home' },
{ value: 'search', label: 'Search', icon: 'search' },
{ value: 'mail', label: 'Mail', icon: 'mail' },
{ value: 'settings', label: 'Settings', icon: 'settings' },
]
let selectedCentered = $state('home')
</script>
<div style="width:100%;max-width:36rem">
<NavigationBar aria-label="Main, centered" arrangement="centered">
{#each destinations as destination (destination.value)}
<NavigationBarItem
label={destination.label}
selected={selectedCentered === destination.value}
onclick={() => (selectedCentered = destination.value)}
>
{#snippet icon()}<Icon>{destination.icon}</Icon>{/snippet}
</NavigationBarItem>
{/each}
</NavigationBar>
</div>
Horizontal items
orientation="horizontal" puts the icon beside the label instead of above it. M3 pairs this
with the centered arrangement once a bar has the width to spare.
<script lang="ts">
import { NavigationBar, NavigationBarItem } from 'noph-ui'
import { Icon } from 'noph-ui/icons'
const destinations = [
{ value: 'home', label: 'Home', icon: 'home' },
{ value: 'search', label: 'Search', icon: 'search' },
{ value: 'mail', label: 'Mail', icon: 'mail' },
{ value: 'settings', label: 'Settings', icon: 'settings' },
]
let selectedCentered = $state('home')
</script>
<div style="width:100%;max-width:36rem">
<NavigationBar aria-label="Main, horizontal items" arrangement="centered">
{#each destinations as destination (destination.value)}
<NavigationBarItem
orientation="horizontal"
label={destination.label}
selected={selectedCentered === destination.value}
onclick={() => (selectedCentered = destination.value)}
>
{#snippet icon()}<Icon>{destination.icon}</Icon>{/snippet}
</NavigationBarItem>
{/each}
</NavigationBar>
</div>
Theming
| Custom property | Description |
|---|---|
--np-navigation-bar-container-color | Background of the bar. |
--np-navigation-bar-item-active-indicator-color | The pill behind the selected icon. |
--np-navigation-bar-item-font-weight | Label weight. |
--np-navigation-bar-item-selected-font-weight | Label weight of the selected item. |
API
NavigationBar attributes
Renders a <nav> and takes its attributes, so give it an aria-label when a page has more than one navigation landmark. bind:element gives you the <nav>.
| Attribute | Type | Default | Description |
|---|---|---|---|
arrangement | 'equal-weight' | 'centered' | 'equal-weight' | Whether items stretch to share the bar equally, or keep their own width and group in the middle. |
NavigationBarItem attributes
Renders a <button>, or an <a> with an href, and
takes that element's attributes.
| Attribute | Type | Default | Description |
|---|---|---|---|
label | string | required | Destination name, shown under the icon. |
icon | Snippet | required | The icon. A navigation bar item always has one. |
selected | boolean | false | Marks the current destination. Sets aria-current="page" and makes the item the bar's
tab stop. |
labelBehavior | 'always' | 'selected' | 'always' | Whether every label shows, or only the selected one. |
orientation | 'vertical' | 'horizontal' | 'vertical' | Whether the icon sits above the label, or beside it. |
badge | boolean | false | Shows a badge on the icon. |
badgeLabel | string | number | undefined | undefined | Badge text. Without it the badge is a small dot. |
badgeAriaLabel | string | undefined | undefined | Announces the badge. Without it the badge is hidden from assistive technology. |