ZUI

Search documentation

Find a page or a heading.

Tab Bar

A bar of top-level destinations fixed to one edge of the viewport, translucent or solid, attached or floating, whose labels can show always, only for the active item, or never.

tsx

Installation

bash

Usage

tsx
tsx

Each item is a link to a route, and the route the app is on sets active. Pass the router's link through render so the item navigates on the client; without it the item is a plain <a>. Give the bar an aria-label naming what its destinations are, since a page can hold more than one nav.

side takes the edges shadcn's Sheet names: bottom and top lay the items in a row, left and right in a column. labels="selected" shows only the active label and labels="none" shows none; a hidden label is visually hidden, so a screen reader still reads every item's name. Inside an element with contain: layout it is fixed to that element instead, as in the preview.

tsx

Clearing the bar and the device

A fixed bar sits over the page, so it can cover the last content and a focused control that scrolls under it. Pad the page by the bar's height on its side, for example pb-24 for a bottom bar, and give anything that takes focus a scroll-margin of the same amount so the browser scrolls it clear of the bar.

On a phone with a home indicator or a notch, the top and bottom bars add env(safe-area-inset-top) or env(safe-area-inset-bottom) to their padding, so the indicator does not cover the items. The browser reports those insets as zero unless the page's viewport meta tag includes viewport-fit=cover:

html

In Next.js, set viewportFit: 'cover' in the viewport export.

Composition

text

API reference

TabBar

A nav that takes every prop a nav takes, and requires aria-label. It has no border: the surface separates by blur and shadow.

translucent is bg-background/70 backdrop-blur, and solid is bg-background. attached runs edge to edge and casts its shadow away from that edge, and floating is a pill 16px in from the edges. It carries data-side, data-orientation (horizontal or vertical), data-labels, data-surface and data-shape, and sets data-slot="tab-bar".

TabBarItem

An a that takes every prop an a takes, and render to render another element, such as the router's link. Its name is its TabBarLabel.

active sets aria-current="page", lights the indicator and keeps the label under labels="selected". In a row the items share the width; in a column each is as wide as the bar. Each is at least 48px high, with its label hidden or not. An aria-describedby the caller passes is kept, and the badge's id is added to it. While it has keyboard focus its TabBarIndicator is ringed, or the item itself when it holds no indicator. It carries data-active while active and sets data-slot="tab-bar-item".

TabBarIndicator

A span that takes every prop a span takes: the 56 by 32 pill behind the icon, tinted while its item is active, muted while pressed and ringed while the item has keyboard focus. It carries data-active and sets data-slot="tab-bar-indicator".

TabBarIcon

A span that takes every prop a span takes, sizing the glyph in its children to 20px. It is hidden from a screen reader, which reads the label. It sets data-slot="tab-bar-icon".

TabBarBadge

Renders shadcn's Badge, destructive, at the indicator's top right, and takes every prop it takes except variant. Its children are the count.

The badge is kept out of the item's name and describes the item instead, with countLabel read after the count, so a screen reader hears "Inbox, link, 3 new". Pass countLabel in the app's language. It sets data-slot="tab-bar-badge".

TabBarLabel

A span that takes every prop a span takes, text-xs font-medium. Its children are the destination's name. Where the bar's labels hides it, it is visually hidden and carries data-hidden. It sets data-slot="tab-bar-label".