Bottom Navigation
Primitive

Bottom Navigation

Navigation landmark with active-destination state for a small set of primary application destinations.

Live behavior

Bottom Navigation in motion

Interactive
Atom behavior · App-owned appearance
Preparing behavior…

Interact with the specimen and inspect the behavior Atom contributes.

waiting for input

When to Use

Use BottomNavigation for a short, stable set of top-level destinations commonly placed at the bottom of a compact application. Prefer links when each item changes the URL. Use NavList for longer or grouped navigation, and use Tabs when switching panels inside the current page rather than navigating to a new destination.

Features

The behavior Atom owns before your product adds appearance.

  • Renders a named nav landmark.
  • Supports link destinations and button-based view changes.
  • Supports controlled and uncontrolled active value.
  • Marks the active destination with aria-current="page".
  • Exposes active, disabled, value, and label-visibility state through data attributes.
  • Supports always-visible, active-only, and fully visually-hidden label intent.
  • Exposes static, sticky, absolute, and fixed positioning intent for styled layers without applying styles.
  • Supports asChild and render on both parts.

Import

tsx
import { BottomNavigation } from "@flowstack-ui/atom";

Anatomy

tsx
<BottomNavigation.Root>
  <BottomNavigation.Item value="home" />
  <BottomNavigation.Item value="search" />
  <BottomNavigation.Item value="profile" />
</BottomNavigation.Root>

API Reference

Root

Renders the nav landmark and owns the active value shared by every Item. The default accessible label can be replaced with a label appropriate to the application.

PropTypeDefault
asChildbooleanfalse
renderRenderProp-
valuestring | null-
defaultValuestring | nullnull
onChange(value: string) => void-
labelVisibility"always" | "active" | "hidden""always"
showLabelsbooleanDeprecated compatibility alias
position"static" | "sticky" | "absolute" | "fixed""static"
ariaLabelstring"Bottom navigation"
ARIA attributeValues
aria-labelValue from ariaLabel
Data attributeValues
[data-slot]"bottom-nav-root"
[data-label-visibility]"always" | "active" | "hidden"
[data-position]"static" | "sticky" | "absolute" | "fixed"

Item

Represents one destination and updates Root's active value when activated. It renders an a when href is provided and a button otherwise. Disabled links omit href and leave the tab order.

PropTypeDefault
asChildbooleanfalse
renderRenderProp-
valuestringRequired
hrefstring-
targetstring-
relstring-
disabledbooleanfalse
ARIA attributeValues
aria-current"page" when the Item is active
aria-disabled"true" when disabled
Data attributeValues
[data-slot]"bottom-nav-item"
[data-state]"active" | "inactive"
[data-value]Item value
[data-active]Present when active
[data-disabled]Present when disabled
[data-label-visible]Present when labels should be visibly presented

labelVisibility is authoritative when both it and the deprecated showLabels prop are supplied. Without labelVisibility, showLabels={false} maps to "active"; every other legacy/default case maps to "always". Atom does not hide label content itself. Styled layers use Root and Item data attributes while retaining the authored text in the accessibility tree.

Examples

tsx
import { BottomNavigation } from "@flowstack-ui/atom";

export function PrimaryDestinations() {
  return (
    <BottomNavigation.Root
      defaultValue="home"
      ariaLabel="Primary destinations"
    >
      <BottomNavigation.Item value="home" href="/home">
        Home
      </BottomNavigation.Item>
      <BottomNavigation.Item value="search" href="/search">
        Search
      </BottomNavigation.Item>
      <BottomNavigation.Item value="profile" href="/profile">
        Profile
      </BottomNavigation.Item>
    </BottomNavigation.Root>
  );
}

Controlled View Selection

tsx
import { useState } from "react";
import { BottomNavigation } from "@flowstack-ui/atom";

export function ControlledDestinations() {
  const [value, setValue] = useState("activity");

  return (
    <>
      <p>Current view: {value}</p>
      <BottomNavigation.Root value={value} onChange={setValue}>
        <BottomNavigation.Item value="activity">Activity</BottomNavigation.Item>
        <BottomNavigation.Item value="messages">Messages</BottomNavigation.Item>
      </BottomNavigation.Root>
    </>
  );
}

Icon-only Presentation Intent

tsx
import { BottomNavigation } from "@flowstack-ui/atom";

export function CompactDestinations() {
  return (
    <BottomNavigation.Root
      ariaLabel="Primary destinations"
      defaultValue="home"
      labelVisibility="hidden"
      position="fixed"
    >
      <BottomNavigation.Item value="home" href="/home">Home</BottomNavigation.Item>
      <BottomNavigation.Item value="search" href="/search">Search</BottomNavigation.Item>
    </BottomNavigation.Root>
  );
}

The styled layer may visually hide both labels, but it must keep the authored text available as each Item's accessible name. position is metadata only; the styled layer applies positioning and the application coordinates page content around an overlay.

Accessibility

WAI-ARIA defines no dedicated Bottom Navigation pattern. Root uses a native navigation landmark and follows WAI-ARIA landmark guidance. Give the landmark a concise name that distinguishes it from other navigation regions.

Items with href use native link behavior and expose aria-current="page" when active. Prefer links for real destinations so browser navigation features continue to work. Button Items retain native button keyboard behavior for application-controlled view changes.

Use labelVisibility="hidden" only when every destination remains clear from its visual icon and accessible name. Atom never removes the label from the DOM.

Changelog

Unreleased

  • Added public Agent Knowledge for component selection, required composition, recurring mistakes, and validation.

0.13.0

  • Added labelVisibility="always|active|hidden" with stable Root/Item data attributes and retained showLabels as a deprecated compatibility alias.
  • Added position="static|sticky|absolute|fixed" intent for styled layers.

0.2.0

  • Changed Root accessible-label prop documentation and typings to use ariaLabel while continuing to render the native aria-label attribute.

0.1.0

  • Initial Atom release.