Skip to content

ada-powerful/capacitor-native-navigation

 
 

Repository files navigation

@capgo/capacitor-native-navigation

Capgo - Instant updates for Capacitor

Native navigation chrome for Capacitor apps. Render a native navbar, native tabbar, and native transition shell over a single full-screen WebView while your framework keeps owning routes and content.

Demo

Native navigation tap flow

Animated native navigation tap flow showing tab selection, push transition, and native back

SVG icon descriptors

Animated native SVG icon demo showing inline SVG icons, native tint, labels, and tab selection

Native styling and zoom options

Animated native navigation options demo showing dynamic colors, selected labels, custom indicators, badges, system Liquid Glass, and zoom transitions

What It Does

  • Renders native top navigation and bottom tab chrome from JavaScript state.
  • Emits native intent events such as navbarBack, navbarItemTap, and tabSelect.
  • Captures WebView snapshots for native-feeling push, back, root, tab, and zoom transition shells.
  • Supports native tab styling controls such as Material You dynamic colors, label visibility, indicators, ripples, badges, and selected icons.
  • Writes CSS variables so web content can scroll behind native bars without being hidden.
  • Works with React, Vue, Angular, Svelte, Solid, vanilla JS, and any router that can call imperative methods.

What It Does Not Do

  • It does not create one native WebView per route.
  • It does not render React/Vue/Svelte icon nodes natively. Icons must be serializable descriptors such as SVG strings, SF Symbols, or native resource names.
  • It does not replace your router. JS still owns route state and web content rendering.

Compatibility

Plugin version Capacitor compatibility Maintained
v8.*.* v8.*.* Yes
v7.*.* v7.*.* On demand
v6.*.* v6.*.* On demand

Install

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

Minimal Usage

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

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

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>',
      },
    },
  ],
});

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>',
      },
    },
  ],
});

NativeNavigation.addListener('tabSelect', ({ id }) => {
  router.navigate(id);
});

Native Tab Styling

await NativeNavigation.setTabbar({
  selectedId: 'home',
  labelVisibilityMode: 'selected',
  indicatorColor: '#0A84FF',
  rippleColor: '#330A84FF',
  badgeBackgroundColor: '#FF3B30',
  badgeTextColor: '#FFFFFF',
  colors: {
    dynamic: true,
    tint: '#0A84FF',
    inactiveTint: '#8E8E93',
  },
  tabs: [
    {
      id: 'home',
      title: 'Home',
      icon: { ios: { sfSymbol: 'house' }, android: { resource: 'ic_home' } },
      selectedIcon: { ios: { sfSymbol: 'house.fill' }, android: { resource: 'ic_home_filled' } },
    },
  ],
});

Transition Flow

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

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

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

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

Use With @capgo/capacitor-transitions

Use @capgo/capacitor-native-navigation for the native navbar, tabbar, safe-area insets, and native intent events. Use @capgo/capacitor-transitions for the WebView page stack underneath that native chrome.

npm install @capgo/capacitor-native-navigation @capgo/capacitor-transitions
npx cap sync

Initialize both packages once when the app starts:

import { NativeNavigation } from '@capgo/capacitor-native-navigation';
import '@capgo/capacitor-transitions';
import { initTransitions, setupRouterOutlet, setDirection } from '@capgo/capacitor-transitions/react';

initTransitions({ platform: 'auto' });

const outlet = document.querySelector('cap-router-outlet');
if (outlet) {
  setupRouterOutlet(outlet, { platform: 'auto', swipeGesture: 'auto' });
}

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

Keep the transition outlet focused on pages. Do not render a web header or footer when native chrome owns those surfaces:

<cap-router-outlet platform="auto" swipe-gesture="auto">
  <cap-page>
    <cap-content slot="content" fullscreen>
      <main class="page">Inbox content</main>
    </cap-content>
  </cap-page>
</cap-router-outlet>
.page {
  min-height: 100dvh;
  padding-top: var(--cap-native-navigation-top);
  padding-bottom: var(--cap-native-navigation-bottom);
}

Drive both packages from the same router actions:

async function openMessage(id: string) {
  setDirection('forward');
  await router.push(`/messages/${id}`);
  await NativeNavigation.setNavbar({
    title: 'Message',
    backButton: { visible: true, title: 'Inbox' },
  });
}

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

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

Pick one animation layer per navigation. For normal route pushes, let @capgo/capacitor-transitions animate the WebView pages and update native bars with setNavbar / setTabbar. For shared-element or zoom routes, use beginZoomTransition / finishZoomTransition from this plugin and skip the web page transition for that navigation.

Zoom Transition

import { beginZoomTransition, finishZoomTransition } from '@capgo/capacitor-native-navigation';

const card = document.querySelector('[data-photo-card]');
if (card) {
  const transition = await beginZoomTransition(card, { cornerRadius: 18 });

  router.navigate('/photo/42');
  await router.ready?.();

  await finishZoomTransition(undefined, {
    id: transition.id,
    cornerRadius: 18,
  });
}

CSS Insets

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

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

.page {
  min-height: 100dvh;
  padding-bottom: calc(var(--cap-native-navigation-bottom) + 24px);
}

Available variables:

  • --cap-native-navigation-top
  • --cap-native-navigation-right
  • --cap-native-navigation-bottom
  • --cap-native-navigation-left
  • --cap-native-navbar-height
  • --cap-native-tabbar-height

Web Components

The package can register optional custom elements for framework-agnostic declarative setup:

import { defineNativeNavigationElements } from '@capgo/capacitor-native-navigation';

defineNativeNavigationElements();
<cap-native-navigation-provider enabled="true" content-inset-mode="css"></cap-native-navigation-provider>

<cap-native-navbar
  title="Home"
  subtitle="Native chrome"
  transparent
  right-items='[{"id":"compose","title":"Compose","icon":{"ios":{"sfSymbol":"square.and.pencil"}}}]'
></cap-native-navbar>

<cap-native-tabbar
  selected-id="home"
  tabs='[{"id":"home","title":"Home","icon":{"ios":{"sfSymbol":"house.fill"}}}]'
></cap-native-tabbar>

Icon Descriptors

const icon = {
  svg: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 10.5 12 3l9 7.5"/></svg>',
  width: 24,
  height: 24,
  template: true,
  src: 'fallback_asset_name',
  ios: {
    svg: '<svg viewBox="0 0 24 24"><path d="M3 10.5 12 3l9 7.5"/></svg>',
    sfSymbol: 'house.fill',
    image: 'BundledAssetName',
  },
  android: {
    svg: '<svg viewBox="0 0 24 24"><path d="M3 10.5 12 3l9 7.5"/></svg>',
    resource: 'ic_menu_view',
    image: 'bundled_drawable_name',
  },
};

Inline SVG supports the icon-focused subset used by common sets such as Lucide and Feather: path, line, polyline, polygon, circle, and rect. The SVG is rendered as a template image by default, so native tint colors can recolor it without bundling a platform asset.

Platform Notes

  • iOS uses UIKit UINavigationBar and UITabBar. iOS 26+ hosts the tabbar in a real UITabBarController so Apple owns the system Liquid Glass background; earlier versions use native translucent/material fallback styling.
  • Android uses an AppCompat Toolbar and a floating native tab capsule with edge-to-edge placement.
  • Web fallback does not draw native bars; it mirrors inset variables and events for development.

Example App

The example-app/ folder is a vanilla JS Capacitor demo linked with file:...

cd example-app
bun install
bun run build
bunx cap add ios
bunx cap add android
bunx cap sync

API

Framework-agnostic native navigation chrome API.

configure(...)

configure(options?: NativeNavigationConfigureOptions | undefined) => Promise<NativeNavigationInsetsResult>

Configure the native chrome host and content inset behavior.

Param Type
options NativeNavigationConfigureOptions

Returns: Promise<NativeNavigationInsetsResult>


setNavbar(...)

setNavbar(options: NativeNavigationNavbarOptions) => Promise<NativeNavigationInsetsResult>

Render or update the native navbar.

Param Type
options NativeNavigationNavbarOptions

Returns: Promise<NativeNavigationInsetsResult>


setTabbar(...)

setTabbar(options: NativeNavigationTabbarOptions) => Promise<NativeNavigationInsetsResult>

Render or update the native tabbar.

Param Type
options NativeNavigationTabbarOptions

Returns: Promise<NativeNavigationInsetsResult>


beginTransition(...)

beginTransition(options?: NativeNavigationBeginTransitionOptions | undefined) => Promise<NativeNavigationTransitionResult>

Capture the current WebView and prepare a native transition.

Param Type
options NativeNavigationBeginTransitionOptions

Returns: Promise<NativeNavigationTransitionResult>


finishTransition(...)

finishTransition(options?: NativeNavigationFinishTransitionOptions | undefined) => Promise<NativeNavigationTransitionResult>

Animate from the captured WebView snapshot to the current live WebView.

Param Type
options NativeNavigationFinishTransitionOptions

Returns: Promise<NativeNavigationTransitionResult>


getPluginVersion()

getPluginVersion() => Promise<PluginVersionResult>

Returns the platform implementation version marker.

Returns: Promise<PluginVersionResult>


addListener('navbarBack', ...)

addListener(eventName: 'navbarBack', listenerFunc: (event: NativeNavigationBackEvent) => void) => Promise<PluginListenerHandle>
Param Type
eventName 'navbarBack'
listenerFunc (event: NativeNavigationBackEvent) => void

Returns: Promise<PluginListenerHandle>


addListener('navbarItemTap', ...)

addListener(eventName: 'navbarItemTap', listenerFunc: (event: NativeNavigationBarItemTapEvent) => void) => Promise<PluginListenerHandle>
Param Type
eventName 'navbarItemTap'
listenerFunc (event: NativeNavigationBarItemTapEvent) => void

Returns: Promise<PluginListenerHandle>


addListener('tabSelect', ...)

addListener(eventName: 'tabSelect', listenerFunc: (event: NativeNavigationTabSelectEvent) => void) => Promise<PluginListenerHandle>
Param Type
eventName 'tabSelect'
listenerFunc (event: NativeNavigationTabSelectEvent) => void

Returns: Promise<PluginListenerHandle>


addListener('safeAreaChanged', ...)

addListener(eventName: 'safeAreaChanged', listenerFunc: (event: NativeNavigationSafeAreaChangedEvent) => void) => Promise<PluginListenerHandle>
Param Type
eventName 'safeAreaChanged'
listenerFunc (event: NativeNavigationSafeAreaChangedEvent) => void

Returns: Promise<PluginListenerHandle>


addListener('transitionStart', ...)

addListener(eventName: 'transitionStart', listenerFunc: (event: NativeNavigationTransitionEvent) => void) => Promise<PluginListenerHandle>
Param Type
eventName 'transitionStart'
listenerFunc (event: NativeNavigationTransitionEvent) => void

Returns: Promise<PluginListenerHandle>


addListener('transitionEnd', ...)

addListener(eventName: 'transitionEnd', listenerFunc: (event: NativeNavigationTransitionEvent) => void) => Promise<PluginListenerHandle>
Param Type
eventName 'transitionEnd'
listenerFunc (event: NativeNavigationTransitionEvent) => void

Returns: Promise<PluginListenerHandle>


Interfaces

NativeNavigationInsetsResult

Returned by methods that may change safe content bounds.

Prop Type
insets NativeNavigationInsets

NativeNavigationInsets

Insets exposed to web content.

Prop Type
top number
right number
bottom number
left number
navbarHeight number
tabbarHeight number

NativeNavigationConfigureOptions

Global plugin configuration.

Prop Type Description
enabled boolean Enables or disables the native chrome host.
platformStyle NativeNavigationPlatformStyle Native style preference. auto uses the current platform.
contentInsetMode NativeNavigationContentInsetMode When css, the plugin writes CSS variables on document.documentElement.
animationDuration number Default native transition duration in milliseconds.
colors NativeNavigationColors Shared color hints for native bars.

NativeNavigationColors

Native bar colors. Use CSS-style hex strings (#RRGGBB or #AARRGGBB).

Prop Type Description
dynamic boolean When true, Android 12+ derives unspecified bar colors from Material You system palettes. Explicit color fields still win.
tint string Tint color for active buttons/items.
inactiveTint string Color for inactive tab items.
background string Optional background tint. Ignored on iOS 26+ so UIKit can preserve the system Liquid Glass navigation appearance.
foreground string Title and label text color where the native platform supports it.
badgeBackground string Badge background color for native tab badges.
badgeText string Badge text color for native tab badges.
indicator string Active tab indicator color on Android.
ripple string Tab press ripple color on Android.

NativeNavigationNavbarOptions

Native navbar state.

Prop Type Description
hidden boolean Hide the native navbar.
title string Main title.
subtitle string Secondary title where supported by the platform.
large boolean Prefer a large iOS title style.
transparent boolean Prefer transparent/scroll-edge style.
blurEffect NativeNavigationBlurEffect iOS blur/material effect for the navbar background when glass is not available. Defaults to systemChromeMaterial for transparent bars.
backButton NativeNavigationBackButton Back button state.
leftItems NativeNavigationBarButton[] Left-side action buttons.
rightItems NativeNavigationBarButton[] Right-side action buttons.
colors NativeNavigationColors Navbar color hints.
animated boolean Animate native navbar changes.

NativeNavigationBackButton

Native back button configuration.

Prop Type Description
visible boolean Show the native back affordance.
title string Optional back title.

NativeNavigationBarButton

A button shown in the native navbar.

Prop Type Description
id string Stable id returned in navbarItemTap.
title string Visible text label.
icon NativeNavigationIcon Native icon descriptor.
enabled boolean Whether the action is enabled. Defaults to true.

NativeNavigationIcon

A serializable icon descriptor. Framework nodes are intentionally not accepted because icons are rendered by native UI.

Prop Type Description
src string Cross-platform asset path or URL fallback.
svg string Cross-platform inline SVG markup. The native renderers support common icon shapes such as path, line, polyline, polygon, circle, and rect. SVG icons are rendered as template images by default so native tint colors still apply.
width number Preferred rendered icon width in native points/dp. Defaults to 24.
height number Preferred rendered icon height in native points/dp. Defaults to 24.
template boolean When true, native tint colors are applied to the rendered SVG/image. Defaults to true.
ios { sfSymbol?: string; image?: string; svg?: string; } iOS-specific SF Symbol, bundled image name, or inline SVG.
android { resource?: string; image?: string; svg?: string; } Android-specific drawable resource, asset name, or inline SVG.

NativeNavigationTabbarOptions

Native tabbar state.

Prop Type Description
hidden boolean Hide the native tabbar.
tabs NativeNavigationTab[] Tab definitions.
selectedId string Currently selected tab id.
labels boolean Show text labels. Defaults to true.
labelVisibilityMode NativeNavigationTabLabelVisibilityMode Native label visibility mode. Overrides labels when provided.
icons boolean Show icons. Defaults to true.
colors NativeNavigationColors Tabbar color hints.
blurEffect NativeNavigationBlurEffect iOS blur/material effect for the tabbar background when glass is not available.
disableTransparentOnScrollEdge boolean Keep the iOS scroll-edge tabbar appearance from becoming transparent. Mirrors Expo Router native tabs' disableTransparentOnScrollEdge option. Defaults to false.
disableIndicator boolean Disable the Android active tab indicator.
indicatorColor string Active tab indicator color on Android. colors.indicator is also supported.
rippleColor string Tab press ripple color on Android. colors.ripple is also supported.
badgeBackgroundColor string Badge background color. colors.badgeBackground is also supported.
badgeTextColor string Badge text color. colors.badgeText is also supported.
animated boolean Animate native tabbar changes.

NativeNavigationTab

A native tab item.

Prop Type Description
id string Stable tab id returned in tabSelect.
title string Visible tab label.
icon NativeNavigationIcon Native icon descriptor.
selectedIcon NativeNavigationIcon Optional selected-state icon.
badge string | number Optional badge. Numeric badges are supported on both platforms; text badge support depends on platform capabilities.
enabled boolean Whether the tab is enabled. Defaults to true.

NativeNavigationTransitionResult

Native transition result.

Prop Type
id string
direction NativeNavigationTransitionDirection
duration number

NativeNavigationBeginTransitionOptions

Begin a native transition transaction before JS changes route content.

Prop Type Description
id string
direction NativeNavigationTransitionDirection
duration number
sourceRect NativeNavigationRect Source rectangle for zoom transitions. Use viewport coordinates such as those returned by Element.getBoundingClientRect().
targetRect NativeNavigationRect Destination rectangle for shared-element-style zoom transitions.
cornerRadius number Corner radius used while animating a zoom transition.

NativeNavigationRect

A rectangle in WebView viewport coordinates, expressed in native points/dp.

Prop Type
x number
y number
width number
height number

NativeNavigationFinishTransitionOptions

Finish a native transition transaction after JS has changed route content.

Prop Type Description
id string
direction NativeNavigationTransitionDirection
duration number
sourceRect NativeNavigationRect Source rectangle for zoom transitions when no active source was recorded.
targetRect NativeNavigationRect Destination rectangle for shared-element-style zoom transitions.
cornerRadius number Corner radius used while animating a zoom transition.

PluginVersionResult

Plugin version payload.

Prop Type Description
version string Version identifier returned by the platform implementation.

PluginListenerHandle

Prop Type
remove () => Promise<void>

NativeNavigationBackEvent

Prop Type
source 'navbar'

NativeNavigationBarItemTapEvent

Prop Type
id string
title string
placement 'left' | 'right'

NativeNavigationTabSelectEvent

Prop Type
id string
index number
title string

NativeNavigationSafeAreaChangedEvent

Prop Type
insets NativeNavigationInsets

NativeNavigationTransitionEvent

Prop Type
id string
direction NativeNavigationTransitionDirection
duration number

Type Aliases

NativeNavigationPlatformStyle

Platform rendering preference for the native bars.

'auto' | 'ios' | 'android'

NativeNavigationContentInsetMode

How the plugin exposes native bar sizes to web content.

'css' | 'none'

NativeNavigationBlurEffect

Native material/blur effect preference.

'none' | 'systemDefault' | 'extraLight' | 'light' | 'dark' | 'regular' | 'prominent' | 'systemUltraThinMaterial' | 'systemThinMaterial' | 'systemMaterial' | 'systemThickMaterial' | 'systemChromeMaterial' | 'systemUltraThinMaterialLight' | 'systemThinMaterialLight' | 'systemMaterialLight' | 'systemThickMaterialLight' | 'systemChromeMaterialLight' | 'systemUltraThinMaterialDark' | 'systemThinMaterialDark' | 'systemMaterialDark' | 'systemThickMaterialDark' | 'systemChromeMaterialDark'

NativeNavigationTabLabelVisibilityMode

Native tab label visibility behavior.

'auto' | 'selected' | 'labeled' | 'unlabeled'

NativeNavigationTransitionDirection

Navigation animation direction.

'forward' | 'back' | 'root' | 'tab' | 'zoom' | 'none'

About

Capacitor plugin for native navigation chrome over a WebView.

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages

  • Swift 36.7%
  • Java 28.3%
  • JavaScript 18.9%
  • TypeScript 13.7%
  • CSS 1.8%
  • Ruby 0.3%
  • HTML 0.3%