---
title: Bottom Nav
description: A fixed mobile tab bar — safe-area aware, real touch targets, honest active state.
category: Navigation
related:
  - /components/app-shell
  - /components/navbar
  - /components/fab
  - /components/sidebar
visibility: guest
draft: false
---

# Bottom Nav

The tab bar app-shaped mobile layouts live on. WireKit's layout family had a
navbar, a sidebar, a header and a footer — but nothing pinned to the bottom of a
phone, which is where a thumb actually is.

:::preview{title="Bottom navigation with an active tab and a badge"}
<div style="position: relative; height: 8rem; width: 22rem; max-width: 100%; margin-inline: auto;">
    <x-wirekit::bottom-nav label="Main" :mobile-only="false" interactive style="position: absolute;">
        <x-wirekit::bottom-nav.item href="#" label="Home" icon="home" active />
        <x-wirekit::bottom-nav.item href="#" label="Search" icon="search" />
        <x-wirekit::bottom-nav.item href="#" label="Inbox" icon="inbox" badge="3" />
        <x-wirekit::bottom-nav.item href="#" label="Profile" icon="user" />
    </x-wirekit::bottom-nav>
</div>
:::

:::source{language="blade"}
<x-wirekit::bottom-nav label="Main" :mobile-only="false" interactive>
    <x-wirekit::bottom-nav.item href="#" label="Home" icon="home" active />
    <x-wirekit::bottom-nav.item href="#" label="Search" icon="search" />
    <x-wirekit::bottom-nav.item href="#" label="Inbox" icon="inbox" badge="3" />
    <x-wirekit::bottom-nav.item href="#" label="Profile" icon="user" />
</x-wirekit::bottom-nav>
:::

## Reserving room for it

The bar is `position: fixed`, so page content scrolls **underneath** it. Without
room reserved, the last thing on every page hides behind the nav.

Add `wk-bottom-nav-offset` to whatever scrolls:

```blade
<main class="wk-bottom-nav-offset">
    …
</main>

<x-wirekit::bottom-nav label="Main">…</x-wirekit::bottom-nav>
```

That is opt-in on purpose: this component cannot know which element scrolls in
your layout, and padding the wrong box is worse than padding none. The offset
disappears at `md` along with the bar itself — add `wk-bottom-nav-offset-always`
if you kept the bar on desktop with `:mobile-only="false"`.

## Items

Each item is a link with an icon, a label and an optional badge:

```blade
<x-wirekit::bottom-nav label="Main">
    <x-wirekit::bottom-nav.item href="/" label="Home" icon="home" :active="request()->is('/')" />
    <x-wirekit::bottom-nav.item href="/inbox" label="Inbox" icon="inbox" badge="3" />
</x-wirekit::bottom-nav>
```

The badge goes into the item's **accessible name** — "Inbox, 3" — because "Inbox"
and "Inbox, 3" are different claims and a reader who cannot see the dot deserves
the number. The visual badge is then hidden from assistive tech, so it is
announced once rather than twice.

## Where it shows

By default the bar hides from `md` up, where a [sidebar](/components/sidebar) or a
[navbar](/components/navbar) does this job — a bar pinned to the bottom of a
27-inch screen is a long way from the content it belongs to.

Pass `:mobile-only="false"` to keep it everywhere.

## Props

### `<x-wirekit::bottom-nav>`

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `label` | `string` | `'Main'` | Names the nav landmark. |
| `mobileOnly` | `bool` | `true` | Hide from `md` up. |
| `interactive` | `bool` | `false` | Track the current tab client-side: clicking an item marks it active (no page load), keyed by label. Off by default — a real app usually drives `active` from its router. |
| `scope` | `string\|null` | `null` | Class-scope override. |

### `<x-wirekit::bottom-nav.item>`

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `href` | `string` | `'#'` | Where it goes. |
| `label` | `string` | `''` | Visible label, and the base of the accessible name. |
| `active` | `bool` | `false` | Marks this as the current page. |
| `icon` | `string\|null` | `null` | Icon name. |
| `badge` | `string\|int\|null` | `null` | Count on the icon. Joins the accessible name. |
| `scope` | `string\|null` | `null` | Class-scope override. |

## Accessibility

- A real `<nav>` landmark with a name, so a screen-reader user can jump to it.
- The current tab carries `aria-current="page"` **and** a marker line above it —
  the color is a third signal, never the only one (WCAG 1.4.1).
- Items are at least 52px tall. A nav item is the primary control here, and a
  32px tab on a phone is a tap people miss (WCAG 2.5.5).
- The bar pads itself by `env(safe-area-inset-bottom)`. The bottom of the viewport
  is not the bottom of the usable screen on a phone with a home indicator — without
  that padding the labels sit under it and the swipe-up gesture eats the taps.
- The badge is announced once, as part of the item's name.

## Keyboard Interaction

| Key | Action |
|-----|--------|
| <kbd>Tab</kbd> | Move through the tabs in order. |
| <kbd>Enter</kbd> | Follow the focused tab. |

The items are ordinary links, so they keep every behavior a link has — middle-click,
open in a new tab, the status bar showing where it goes.
