Capstart

Native Bar

Add a native navbar, tabbar, and transition shell to a Capacitor app.

@capgo/capacitor-native-navigation renders a native top navigation bar, a native bottom tab bar, and native transition shells with iOS and Android UI components.

Your web app keeps control of routes, pages, and content. The plugin only owns the native frame around the WebView: bars, buttons, tabs, safe areas, and transition animations.

Demo

What It Does

  • Renders a native navbar at the top of the screen.
  • Renders a native tabbar at the bottom of the screen.
  • Emits JavaScript events when the user taps back, a navbar action, or a tab.
  • Exposes CSS variables so web content does not hide behind native bars.
  • Captures the WebView to create transitions that feel closer to a native app.
  • Works with React, Vue, Angular, Svelte, Solid, vanilla JavaScript, and any router that can call imperative methods.

What It Does Not Replace

  • It does not replace your router.
  • It does not create one native WebView per route.
  • It does not turn React, Vue, or Svelte components into native icons. Icons must be serializable descriptors: SVG strings, iOS SF Symbols, or Android resources.

Installation

bun add @capgo/capacitor-native-navigation
bunx cap sync

The current package targets Capacitor 8 with @capacitor/core >= 8.0.0.

Minimal Configuration

Initialize the plugin when the app starts. contentInsetMode: 'css' tells the plugin to write native insets into CSS variables.

native-navigation.ts
import { NativeNavigation } from '@capgo/capacitor-native-navigation';

await NativeNavigation.configure({
  contentInsetMode: 'css',
  animationDuration: 360,
});

Native Navbar

The navbar is updated from JavaScript. It can display a title, subtitle, back button, and actions.

await NativeNavigation.setNavbar({
  title: 'Home',
  subtitle: 'Native chrome',
  transparent: true,
  backButton: { visible: false },
  rightItems: [
    {
      id: 'compose',
      title: 'Compose',
      icon: {
        svg: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg>',
      },
    },
  ],
});

Native Tabbar

The tabbar receives a serializable list of tabs. The plugin renders the active tab, labels, badges, icons, and native colors.

await NativeNavigation.setTabbar({
  selectedId: 'home',
  labelVisibilityMode: 'labeled',
  icons: true,
  colors: {
    dynamic: true,
  },
  tabs: [
    {
      id: 'home',
      title: 'Home',
      icon: {
        svg: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 10.5 12 3l9 7.5"/><path d="M5 10v10h14V10"/></svg>',
      },
    },
    {
      id: 'settings',
      title: 'Settings',
      icon: {
        svg: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M12 2v3M12 19v3M2 12h3M19 12h3"/></svg>',
      },
    },
  ],
});

Connect Your Router

Native bars do not navigate by themselves. They emit user intent, then your router changes the web page.

await NativeNavigation.addListener('navbarBack', () => {
  router.back();
});

await NativeNavigation.addListener('navbarItemTap', ({ id }) => {
  if (id === 'compose') router.push('/compose');
});

await NativeNavigation.addListener('tabSelect', ({ id }) => {
  router.push(`/${id}`);
});

CSS Insets

With contentInsetMode: 'css', the plugin updates variables on document.documentElement.

.app-scroll {
  height: 100dvh;
  overflow: auto;
  padding-top: calc(var(--cap-native-navigation-top) + 24px);
  padding-bottom: calc(var(--cap-native-navigation-bottom) + 24px);
}

Available variables:

VariableRole
--cap-native-navigation-topSpace occupied by the navbar and top safe area.
--cap-native-navigation-rightNative space on the right.
--cap-native-navigation-bottomSpace occupied by the tabbar and bottom safe area.
--cap-native-navigation-leftNative space on the left.
--cap-native-navbar-heightHeight of the native navbar.
--cap-native-tabbar-heightHeight of the native tabbar.

Transitions

A native transition wraps a normal web navigation: capture the current state, change the route, then finish the animation.

const transition = await NativeNavigation.beginTransition({
  direction: 'forward',
});

router.push('/detail');
await router.ready?.();

await NativeNavigation.setNavbar({
  title: 'Detail',
  backButton: { visible: true, title: 'Back' },
});

await NativeNavigation.finishTransition({
  id: transition.id,
  direction: 'forward',
});

Platforms

PlatformImplementation
iOSUINavigationBar and UITabBar. iOS 26+ uses the system Liquid Glass behavior for the tabbar.
AndroidAppCompat Toolbar and a native tabbar with edge-to-edge placement.
WebNo native bars; useful development fallback with events and inset variables.

Core API

Method or eventUsage
configure()Enables the native host and configures insets.
setNavbar()Updates the native navbar.
setTabbar()Updates the native tabbar.
beginTransition()Captures the WebView before a route change.
finishTransition()Animates toward the updated WebView.
getPluginVersion()Returns the native plugin version marker.
navbarBackEvent for the native back button.
navbarItemTapEvent for a navbar action button.
tabSelectEvent for tab selection.
safeAreaChangedEvent for inset changes.
transitionStart / transitionEndEvents for the native transition lifecycle.

On this page

Need help with your app?

Our team can help you integrate Capstart.