@capgo/capacitor-native-navigation
v8.0.24
Published
Capacitor plugin for native navbar, tabbar, safe-area handling, and WebView snapshot transitions.
Maintainers
Readme
@capgo/capacitor-native-navigation
Native navbar, tabbar, safe-area handling, and WebView snapshot transitions for Capacitor apps. Your web framework keeps routing and page rendering while this plugin owns the platform surfaces users expect to feel native.
Demo
Native navigation tap flow
SVG icon descriptors
Native styling and zoom options
Features
- Drive native top navigation and bottom tabs from JavaScript state.
- Use system-owned iOS navigation bars, tab bars, tab gestures, and Liquid Glass rendering.
- Emit native intent events such as
navbarBack,navbarItemTap, andtabSelect. - Capture WebView snapshots for native-feeling push, back, root, tab, and zoom transitions.
- Configure tab labels, selected icons, badges, indicators, ripples, tint colors, and dynamic colors.
- Write CSS inset variables so web content can scroll behind native bars without being hidden.
- Work with React, Vue, Angular, Svelte, Solid, vanilla JS, and any router that exposes imperative navigation.
How It Fits
- Your router still owns route state and page rendering.
- The app still uses one full-screen WebView.
- Icons must be serializable descriptors such as SVG strings, SF Symbols, or native resource names.
Compatibility
@capgo/capacitor-native-navigation targets Capacitor 8 and Node.js 22+.
Install
npm install @capgo/capacitor-native-navigation
npx cap syncMinimal 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>',
},
},
],
});
await 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 syncInitialize 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: 'shared_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,UITabBar, andUITabBarControllerso the system owns tab interaction, Liquid Glass rendering, and safe-area behavior. - Android uses an AppCompat
Toolbarand a native bottom tab surface with edge-to-edge placement. - Web mirrors inset variables and events for local development.
Example App
The example-app/ folder is a vanilla JS Capacitor demo linked with file:...
cd example-app
npm install
npm run build
npx cap add ios
npx cap add android
npx cap syncAPI
configure(...)setNavbar(...)setTabbar(...)beginTransition(...)finishTransition(...)getPluginVersion()addListener('navbarBack', ...)addListener('navbarItemTap', ...)addListener('tabSelect', ...)addListener('safeAreaChanged', ...)addListener('transitionStart', ...)addListener('transitionEnd', ...)- Interfaces
- Type Aliases
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'
