Skip to content

Getting Started

GitHub

You can use our AI-Assisted Setup to install the plugin. Add the Capgo skills to your AI tool using the following command:

Terminal window
npx skills add https://github.com/Cap-go/capgo-skills --skill capacitor-plugins

Then use the following prompt:

Use the `capacitor-plugins` skill from `Cap-go/capgo-skills` to install the `@capgo/capacitor-native-navigation` plugin in my project.

If you prefer Manual Setup, install the plugin by running the following commands and follow the platform-specific instructions below:

  1. Install the package

    Terminal window
    npm i @capgo/capacitor-native-navigation
  2. Sync native projects

    Terminal window
    npx cap sync
  3. Configure native chrome

    import { NativeNavigation } from '@capgo/capacitor-native-navigation';
    await NativeNavigation.configure({
    contentInsetMode: 'css',
    animationDuration: 360,
    colors: {
    tint: '#0f172a',
    inactiveTint: '#64748b',
    },
    });
  4. Render the native navbar

    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>',
    },
    },
    ],
    });
  5. Render the native tabbar

    await NativeNavigation.setTabbar({
    selectedId: 'home',
    labelVisibilityMode: 'selected',
    icons: true,
    colors: {
    dynamic: true,
    tint: '#0f172a',
    inactiveTint: '#64748b',
    },
    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',
    badge: '2',
    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>',
    },
    },
    ],
    });
  6. Handle native intent events

    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}`);
    });

Pass segments to setNavbar to stack rows of native segments under the bar, for example a scope switcher on top and section tabs below it. Rows draw top to bottom and each one adds 44pt (iOS) or 48dp (Android) to the navbar and to the reported insets. iOS uses UISegmentedControl and glass pills; Android uses Material segmented buttons and filter chips.

iOSAndroid
iOS recording: tapping Account sections, switching to Organization swaps the second row to eight scrolling chips, selecting BillingAndroid recording: segmented Account / Organization row over scrolling section chips, switching scope and selecting Billing
const sections = {
account: ['General', 'Notifications', 'Password', 'Two-factor'],
organization: ['General', 'Members', 'Plans', 'Usage', 'Credits', 'Billing', 'Security', 'Webhooks'],
};
const selection = { scope: 'account', section: 'general' };
const renderNavbar = () =>
NativeNavigation.setNavbar({
title: 'Settings',
segments: [
{
id: 'scope',
selectedId: selection.scope,
items: [
{ id: 'account', title: 'Account' },
{ id: 'organization', title: 'Organization' },
],
},
{
id: 'section',
style: 'scroll', // scrolling chips; `auto` uses `segmented` for up to 4 items
selectedId: selection.section,
items: sections[selection.scope].map((title) => ({ id: title.toLowerCase(), title })),
},
],
});
await NativeNavigation.addListener('navbarSegmentSelect', async ({ groupId, id }) => {
if (groupId === 'scope') {
selection.scope = id;
selection.section = 'general'; // the second level depends on the first one
} else {
selection.section = id;
}
await renderNavbar();
});
await renderNavbar();

Selection moves natively when the user taps, but every setNavbar call applies selectedId again. Keep the selected ids in your state and pass them on every later setNavbar call; to reject a selection, send the previous selectedId. Items also accept icon, accessibilityLabel, and enabled. Pass segments: [] to remove the rows. Available since 8.13.0.

Native transitions are a transaction around your normal JavaScript route change:

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

Use the zoom helpers for card-to-detail or media-preview flows. Pass the tapped element before your router changes content, then finish after the detail page is ready.

import { beginZoomTransition, finishZoomTransition } from '@capgo/capacitor-native-navigation';
const card = document.querySelector('[data-message-card]');
if (card) {
const transition = await beginZoomTransition(card, { cornerRadius: 18 });
router.push('/message/42');
await router.ready?.();
await NativeNavigation.setNavbar({
title: 'Message',
backButton: { visible: true, title: 'Inbox' },
});
await finishZoomTransition(undefined, {
id: transition.id,
cornerRadius: 18,
});
}

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

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

Initialize both packages once:

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 cap-router-outlet focused on pages, not duplicate web bars:

<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>

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 route change. Let @capgo/capacitor-transitions animate normal page pushes, and use Native Navigation’s zoom helpers only for shared-element or zoom routes.

With contentInsetMode: 'css', the plugin writes native bar dimensions to document.documentElement.

.page {
padding-top: var(--cap-native-navigation-top);
padding-bottom: var(--cap-native-navigation-bottom);
}

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

Icons must be serializable because native UI renders them. You can use cross-platform SVG, platform-specific SVG, SF Symbols, bundled iOS images, Android drawable resources, or bundled Android images.

const icon = {
svg: '<svg viewBox="0 0 24 24"><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 icon sets such as Lucide and Feather: path, line, polyline, polygon, circle, and rect. SVG icons are rendered as template images by default, so native tint colors can recolor them.

The package can register 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"
transparent
right-items='[{"id":"compose","title":"Compose","icon":{"svg":"<svg viewBox=\"0 0 24 24\"><path d=\"M12 20h9\"/></svg>"}}]'
></cap-native-navbar>
<cap-native-tabbar
selected-id="home"
tabs='[{"id":"home","title":"Home","icon":{"ios":{"sfSymbol":"house.fill"}}}]'
></cap-native-tabbar>

On iPhone Duo, iOS often places the system tab bar and status bar in a vertical side rail. env(safe-area-inset-top) can be 0 while left or right grows. Set viewport-fit=cover on your viewport meta tag when you pad with CSS env anywhere in the app. Test folded, open, landscape, and Split View poses in Xcode Device Hub.

Native Navigation uses real UINavigationBar and UITabBar, so system tab bars follow the rail. The curve tab bar shape stays on the bottom; the center action keeps a circular cradle when the window widens.

Two open landscape layouts. On the left, the system tab bar sits in the side rail. On the right, the curve tab bar stays along the bottom, and the center action sits in a circular cradle.

With contentInsetMode: 'css', pad web content with plugin variables. Use max() with env fallbacks per edge so layout stays correct when a plugin variable is 0 without summing duplicate insets:

main {
padding-top: max(var(--cap-native-navigation-top, 0px), env(safe-area-inset-top, 0px));
padding-right: max(var(--cap-native-navigation-right, 0px), env(safe-area-inset-right, 0px));
padding-bottom: max(var(--cap-native-navigation-bottom, 0px), env(safe-area-inset-bottom, 0px));
padding-left: max(var(--cap-native-navigation-left, 0px), env(safe-area-inset-left, 0px));
}

Listen for safeAreaChanged (or capNativeNavigation:safeAreaChanged on window) when the rail side or fold state changes. On hosted system tab bars, insets.tabbarHeight may be 0 to avoid double bottom padding; use insets.systemTabbarHeight or --cap-native-system-tabbar-height for FAB offsets.

Capacitor-wide guidance (viewport-fit, System Bars, Status Bar, testing): Supporting iPhone Duo in Capacitor apps.

  • iOS renders UINavigationBar and UITabBar; iOS 26+ uses the system Liquid Glass bar behavior.
  • Android renders an AppCompat toolbar and Material bottom navigation.
  • Web fallback does not draw native bars. It mirrors events and inset variables for browser development.
  • The plugin keeps one full-screen Capacitor WebView. Native owns the frame, bars, safe-area reporting, and transition shell.

If you are using Getting Started to plan native media and interface behavior, connect it with Using @capgo/capacitor-native-navigation for the native capability in Using @capgo/capacitor-native-navigation, Using @capgo/capacitor-live-activities for the native capability in Using @capgo/capacitor-live-activities, @capgo/capacitor-live-activities for the implementation detail in @capgo/capacitor-live-activities, Using @capgo/capacitor-video-player for the native capability in Using @capgo/capacitor-video-player, and @capgo/capacitor-video-player for the implementation detail in @capgo/capacitor-video-player.