<!-- Generated by scripts/gen-docs.mts from the JSDoc in registry/. Do not edit; run `pnpm run docs`. -->

# TabBar

The bar of tabs at the bottom of a phone screen: two to five of the app's top-level pages, each an icon over a short label, with the current page marked `aria-current="page"` and an accent wash. An `accessory`, such as Search, sits apart in a circle of its own, as in iOS. The bar is navigation only and the same on every page: put page actions (create, share, edit) on the page itself, never in the bar, so a tab never changes meaning.

```tsx
import { TabBar } from "@/components/ouro/tab-bar/tab-bar";
```

## Example

```tsx
<TabBar
  label="Main"
  currentPath={pathname}
  items={[
    { label: "Home", href: "/", icon: <House />, paths: ["/account"] },
    { label: "Recipes", href: "/recipes", icon: <BookOpen /> },
    { label: "Shopping", href: "/shopping-list", icon: <ShoppingBag /> },
  ]}
  accessory={{ label: "Search", href: "/search", icon: <Search /> }}
/>
```

## Usage

The bar is fixed to the bottom of the screen, clear of the home indicator, and the component keeps its room at the end of the page, so render it last in the layout and the content scrolls clear of it. It hides from `hideFrom` (640px by default), where the app's `Navbar` or `Sidebar` takes over, and when printing. While the on-screen keyboard is up it slides away below the screen (with reduced motion it fades), so it never rides on the keyboard over the field a person types in, and it comes back when the keyboard goes. To hide it on a page that fills the screen (a full-screen editor, a single decision), don't render it there.

Tabs render through `OuroProvider`'s `linkComponent`, so your router navigates. Each tab is 64px tall, well over the 44px a finger needs.

## Use instead when

- Use `Navbar` instead for the links across the top of a wider screen, `Sidebar` for many destinations, and `Tabs` for views of one page.

## Props

Type: `TabBarProps`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `accessory` | `TabBarItem` |  | An item that sits apart, in a circle of its own at the end of the bar, as iOS sets Search apart from the tabs. Only its icon shows; its `label` names it for screen readers. |
| `className` | `string` |  | Layout only: rarely needed, since the bar places itself. Appearance classes warn in development. |
| `currentPath` | `string` |  | The path of the page being shown, from your router (`useLocation().pathname`, or `usePathname()` in Next.js). The tab whose `href` or `paths` match it, or else the longest one above it (`/recipes` for `/recipes/12`), gets `aria-current="page"` and the current look. |
| `hideFrom` | `"sm" \| "md" \| "lg" \| "xl" \| "never"` | `"sm"` | The breakpoint from which the bar hides, because the app's `Navbar` or `Sidebar` takes over there. `never` keeps it at every width. |
| `items` (required) | `readonly TabBarItem[]` |  | The tabs, in order: two to five of the app's top-level pages. |
| `label` (required) | `string` |  | Names the navigation for screen readers ("Main"). It is not shown. |
| `ref` | `Ref<HTMLElement>` |  | Reaches the `<nav>`. |

## Don't use

These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.

- `children`: Not rendered. The bar is `items` and `accessory`.
- `links`: Not a prop. Ouro UI uses `items`: `<TabBar label="Main" items={[{ label, href, icon }]} />`.
- `pathname`: Not a prop. Ouro UI uses `currentPath`: `<TabBar currentPath={pathname}>`.
- `tabs`: Not a prop. Ouro UI uses `items`: `<TabBar label="Main" items={[{ label, href, icon }]} />`.
- `value`: Not a prop. The current tab follows `currentPath`, or an item's `current`: `<TabBar currentPath={pathname}>`.

## `className` is for layout only

Pass margins, width and grid placement (`mt-4`, `w-full`, `col-span-2`).

Appearance comes from props, not classes. In development, a class that sets appearance logs one warning per component instance, naming the prop to use instead. A class sets appearance when, after its variants (`hover:`, `sm:`) and any `!` are removed, it starts with `bg-`, `text-`, `border-`, `rounded-`, `shadow-`, `font-`, `ring-`, `from-`, `via-`, `to-`, `fill-` or `stroke-`, or is `border`, `rounded`, `shadow` or `ring`, or has a `dark:` variant. Text wrapping, overflow and table layout classes (`text-wrap`, `text-ellipsis`, `border-collapse`) are layout and do not warn.

Alignment classes (`text-center`) warn too: set alignment on a parent element.

## Types

### `TabBarItem`

One tab in a TabBar: a page of the app, with its icon and a short label.

```ts
interface TabBarItem {
  /** The tab's text, one short word under the icon: "Recipes". Screen readers hear it as the link's name. */
  label: string;
  /** Where the tab goes. Internal paths go through `OuroProvider`'s `linkComponent` (your router's link). */
  href: string;
  /** The tab's icon, an `<svg>` (a lucide-react icon, for example). It is hidden from screen readers; the label names the tab. */
  icon: ReactNode;
  /**
   * Other paths whose pages belong to this tab, so it stays current there: a Home tab that
   * also owns `/account`. Each matches its own path and the pages under it.
   */
  paths?: readonly string[] | undefined;
  /** Marks this tab as the current page (`aria-current="page"`), when the paths cannot tell. */
  current?: boolean | undefined;
}
```
