suiss/uim · React Native Component

Tab Bar

A bottom navigation bar of icon and label tabs, with optional badges.

Tab Bar is an open-source mobile component from suiss/uim for React Native and Expo, with SwiftUI and Jetpack Compose versions built from the same design tokens. The @esuiss/uim CLI copies its source into your project, so you own the code.

iOS
Tab Bar on iOS in light mode

TabBar is built on the tabs primitive and sits at the bottom of a screen. TabBarList is a rounded capsule with a shadow, inset from the screen edges, so the bar floats above the page. The root adds bottom padding for the safe area, unless an enclosing Screen already handles the bottom edge. TabBarItem stacks an icon over a short label; the active tab sits on a bg-control pill and uses the accent color and a filled icon weight.

Use it for switching between the top-level sections of an app. For switching views within a screen, use Tabs.

Installation

$ pnpm dlx @esuiss/uim@latest add tab-bar

The CLI also adds what it builds on: Icon, Screen, Text.

Manual

  1. Install the dependencies:
npx expo install cn phosphor-react-native react-native-safe-area-context react-native-svg
  1. Add the pieces it builds on: Icon, Screen, Text, and the primitives components/primitives/haptics.tsx, components/primitives/tabs.tsx, components/primitives/use-render.tsx.

  2. Copy the source into components/ui/tab-bar.tsx:

uniwind/components/ui/tab-bar.tsx
import { cn } from "cn"
import type { Icon as PhosphorIcon } from "phosphor-react-native"
import * as React from "react"
import { Pressable, View } from "react-native"
import { useSafeAreaInsets } from "react-native-safe-area-context"
import { useHaptics } from "@/components/primitives/haptics"
import * as TabsPrimitive from "@/components/primitives/tabs"
import { resolveClassName } from "@/components/primitives/use-render"
import { Icon } from "@/components/ui/icon"
import { ScreenContext } from "@/components/ui/screen"
import { Text, TextClassContext } from "@/components/ui/text"

type TabBarBadgeValue = number | string | boolean

function badgeText(badge: TabBarBadgeValue | undefined) {
  if (badge === undefined || badge === false || badge === true) return undefined
  1. Update the import paths to match your project.

Usage

import { TabBar, TabBarList, TabBarItem } from "@/components/ui/tab-bar"
<TabBarExample />

Put TabBarItem entries inside a TabBarList, inside TabBar. Give each item a value, an icon and a label as children. Control the active tab with value and onValueChange, or set defaultValue.

The badge prop shows a count, text or a dot: numbers above 99 show as 99+, 0 and empty strings show nothing, and true shows a small dot. A selection haptic plays whenever the tab changes.

The TabBarBadgeValue type is also exported.

Examples

Without badges

The tab bar without badges.

iOS
Tab Bar (without badges) on iOS in light mode

Large badge

A count above 99 shown as 99+ and a text badge.

iOS
Tab Bar (large badge) on iOS in light mode

Disabled item

A tab bar with one disabled tab.

iOS
Tab Bar (disabled item) on iOS in light mode

API Reference

TabBar

Root that holds the active tab, the side inset and the safe area padding.

ExtendsReact.ComponentProps<typeof TabsPrimitive.Root>

TabBarList

Capsule that holds the tabs.

ExtendsReact.ComponentProps<typeof TabsPrimitive.List>

TabBarItem

One tab with an icon, a label and an optional badge.

ExtendsReact.ComponentProps<typeof TabsPrimitive.Tab>
PropType
About
Phosphor icon shown above the label. Uses the fill weight when active.
Type
PhosphorIcon
Default
—
About
Number, text or true for a dot, shown on the icon's corner.
Type
TabBarBadgeValue
Default
—

Accessibility

  • The list has the tablist role and each item has the tab role, with selected and disabled states.
  • A badge with a count or text is exposed as the tab's accessibility value, so VoiceOver and TalkBack read it after the label.
  • A dot badge is visual only and is not announced.

Notes

  • The capsule uses bg-surface-raised with a border-label/5 border (border-label/10 in dark mode) and a large shadow. The active tab uses bg-control and text-accent, and badges use bg-danger; colors follow the system appearance.
  • The bottom padding is the remaining safe area inset minus 8 points, and never less than 12 points.