Bottom Navigation
Mobile tab bar: a labelled navigation landmark whose active destination carries aria-current="page".
| Package | @vizejs/ui/bottom-navigation |
| Maturity | stable |
| Own the source | vize lib pull bottom-navigation |
| Requires | context, controllable-state |
| Aliases | tab bar, bottom tabs, bottom nav, app navigation bar |
| Covers | HTML nav landmark, aria-current=page, Material bottom navigation |
Minimal setup
Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).
vp install @vizejs/ui
Add the public import below to <script setup lang="ts">. Components provide behavior without styles; the preview uses optional Paper styles.
import { BottomNavigation, TabBar, BottomNavigationItem, TabBarItem } from "@vizejs/ui/bottom-navigation";
Or copy the source into your project with vize lib pull bottom-navigation (see Source Distribution).
Try it
This is the basic example maintained with the component source, using the public package imports. The preview adds the optional Paper styles.
Open the interactive example · Styling these components
Copy the example
<!-- Controlled bottom tab bar that switches between app sections and shows an unread badge. -->
<script setup lang="ts">
import { ref } from "vue";
import { BottomNavigation, BottomNavigationItem } from "@vizejs/ui/bottom-navigation";
type Section = "home" | "search" | "inbox" | "profile";
const sections: readonly Section[] = ["home", "search", "inbox", "profile"];
const active = ref<Section>("home");
</script>
<template>
<div>
<output>Showing: {{ active }}</output>
<BottomNavigation v-model="active" :destinations="sections" aria-label="Main sections">
<BottomNavigationItem value="home">Home</BottomNavigationItem>
<BottomNavigationItem value="search">Search</BottomNavigationItem>
<BottomNavigationItem value="inbox" :badge="3">Inbox</BottomNavigationItem>
<BottomNavigationItem value="profile">Profile</BottomNavigationItem>
</BottomNavigation>
</div>
</template>
API
BottomNavigation<Value extends string>
Source: src/families/navigation/bottom-navigation/bottom-navigation.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
destinations (required) |
readonly Value[] |
required |
Destination values in order; their literal union types v-model and select. |
modelValue |
Value |
undefined |
Controlled active destination. undefined selects uncontrolled behavior. |
defaultValue |
Value |
undefined |
Initial uncontrolled destination. |
ariaLabel |
string |
"Primary" |
Accessible name of the navigation landmark. |
ariaLabelledby |
string |
undefined |
Ids that label the navigation landmark (overrides ariaLabel). |
Events
| Event | Payload | Description |
|---|---|---|
update:modelValue |
[value: Value] |
Fired when an item requests to become the active destination. |
select |
[value: Value, nativeEvent: MouseEvent] |
Fired after an item is activated, with its value and the native click. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
{ readonly active: Value | undefined } |
Renders BottomNavigationItem destinations with the active value. |
TabBar<Value extends string>
Source: src/families/navigation/bottom-navigation/bottom-navigation.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
destinations (required) |
readonly Value[] |
required |
Destination values in order; their literal union types v-model and select. |
modelValue |
Value |
undefined |
Controlled active destination. undefined selects uncontrolled behavior. |
defaultValue |
Value |
undefined |
Initial uncontrolled destination. |
ariaLabel |
string |
"Primary" |
Accessible name of the navigation landmark. |
ariaLabelledby |
string |
undefined |
Ids that label the navigation landmark (overrides ariaLabel). |
Events
| Event | Payload | Description |
|---|---|---|
update:modelValue |
[value: Value] |
Fired when an item requests to become the active destination. |
select |
[value: Value, nativeEvent: MouseEvent] |
Fired after an item is activated, with its value and the native click. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
{ readonly active: Value | undefined } |
Renders BottomNavigationItem destinations with the active value. |
BottomNavigationItem
Source: src/families/navigation/bottom-navigation/bottom-navigation-item.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
value (required) |
string |
required |
Destination value; must be one of the root destinations. |
href |
string |
undefined |
Link target. With href the item renders an <a>, otherwise a <button>. |
disabled |
boolean |
false |
Disable the destination. |
badge |
string | number |
undefined |
Badge text announced with the label (for example an unread count). |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
{ readonly active: boolean } |
Icon and label contents with the active state. |
badge |
{ readonly badge: string | number } |
Visual badge; its text is also announced through aria-describedby via badge. |
TabBarItem
Source: src/families/navigation/bottom-navigation/bottom-navigation-item.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
value (required) |
string |
required |
Destination value; must be one of the root destinations. |
href |
string |
undefined |
Link target. With href the item renders an <a>, otherwise a <button>. |
disabled |
boolean |
false |
Disable the destination. |
badge |
string | number |
undefined |
Badge text announced with the label (for example an unread count). |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
{ readonly active: boolean } |
Icon and label contents with the active state. |
badge |
{ readonly badge: string | number } |
Visual badge; its text is also announced through aria-describedby via badge. |
Behavior
Normative state x input -> outcome table for bottom-navigation.vue and
bottom-navigation-item.vue (@vizejs/ui/bottom-navigation, also exported as
TabBar/TabBarItem). A bottom tab bar is navigation, not an APG tablist: it
renders a labelled <nav> landmark and marks the current destination with
aria-current="page". Every row is proven by the named test in
bottom-navigation.test.ts or bottom-navigation-ssr.test.ts.
| # | State | Input | Outcome | Proven by |
|---|---|---|---|---|
| BN1 | seeded | render | <nav aria-label="Primary">; items with href render links, others type="button"; badges render; current item has aria-current="page" |
renders a labelled navigation landmark with links, buttons, and badges |
| BN2 | uncontrolled | click | the item becomes current (aria-current, data-state="active") and emits update:modelValue then select |
activating an item moves aria-current and emits typed selections |
| BN3 | controlled / disabled / unknown | click | controlled values win; disabled buttons are native-disabled, disabled links drop href with aria-disabled; unknown values never emit |
controlled values win, and disabled or unknown items do not activate |
| BN4 | no provider | setup | items throw VIZE_UI_CONTEXT_MISSING: BottomNavigation |
items require a BottomNavigation provider |
| BN5 | SSR / hydration | isolated requests | byte-identical markup and hydration without diagnostics | renders byte-identical tab bar markup and hydrates without mismatches |
Pair with SafeArea (edges: ["bottom"]) to clear home indicators. The subpath
ships no CSS.