npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

capacitor-navigation

v0.4.0

Published

Native navigation bars (nav bar + tab bar) and back event bridging for Capacitor

Downloads

518

Readme

capacitor-navigation

Native navigation primitives for Capacitor apps:

  • Top navigation bar
  • Bottom tab bar
  • Back gesture/back press event bridge
  • Layout metrics API for anchoring web UI to native chrome

This plugin is designed for SPA routers. Native bars are rendered by iOS/Android, and page transitions remain in your web layer.

Install

pnpm add capacitor-navigation
npx cap sync

Import

import { Navigation } from 'capacitor-navigation'

Quick Start

1) Show a native top bar

await Navigation.setNavigationBar({
  title: 'Home',
  subtitle: 'Native toolbar',
  showBack: true,
  shadow: true,
  rightButtons: [
    { id: 'search', icon: 'search', label: 'Search' },
    { id: 'settings', icon: 'settings', label: 'Settings' },
  ],
})

2) Show a native tab bar

await Navigation.setTabBar({
  items: [
    { id: 'home', label: 'Home', icon: 'house', androidIcon: 'home' },
    { id: 'feed', label: 'Feed', icon: 'list.bullet', androidIcon: 'list' },
    { id: 'profile', label: 'Profile', icon: 'person', androidIcon: 'person' },
  ],
  selected: 'home',
  tintColor: '#007AFF',
  darkTintColor: '#0A84FF',
  insetContent: true, // content stays above tab bar
})

3) Route in JS when native back happens

import { useRouter } from 'vue-router'

const router = useRouter()

const l1 = await Navigation.addListener('backPress', async () => {
  if (window.history.length > 1) await router.back()
})

const l2 = await Navigation.addListener('backTap', async () => {
  if (window.history.length > 1) await router.back()
})

API

Methods

  • setNavigationBar(options)
  • hideNavigationBar()
  • setTabBar(options)
  • updateTab({ id, badge?, label? })
  • selectTab({ id })
  • hideTabBar()
  • setBackGesture(options)
  • getLayoutMetrics()
  • setStatusBar(options)
  • setColorScheme({ scheme: 'light' | 'dark' | 'auto' })
  • getColorScheme(){ scheme: 'light' | 'dark' }
  • removeAllListeners()

Events

  • backGesture
  • backGestureProgress with { state, progress, committed }
  • backPress
  • backTap
  • tabSelect with { id }
  • navActionTap with { id }
  • layoutMetricsChange with NavigationLayoutMetrics
  • colorSchemeChange with { scheme: 'light' | 'dark' }

Layout Metrics

Use native metrics to position floating web UI under header or above tab bar.

type NavigationLayoutMetrics = {
  viewportWidth: number
  viewportHeight: number
  safeAreaTop: number
  safeAreaBottom: number
  navBarVisible: boolean
  navBarTop: number
  navBarHeight: number
  navBarBottom: number
  tabBarVisible: boolean
  tabBarTop: number
  tabBarHeight: number
  tabBarBottom: number
  contentTop: number
  contentBottom: number
  contentHeight: number
}

Example:

const metrics = await Navigation.getLayoutMetrics()
const top = metrics.navBarBottom + 8
const bottomPanelTop = metrics.tabBarTop - 56 - 10

The plugin also injects CSS variables:

  • --cap-nav-height
  • --cap-tab-height
  • --cap-content-top
  • --cap-content-bottom
  • --cap-content-height
  • --cap-nav-bottom
  • --cap-tab-top

Scroll Mode — Full vs Between Bars

By default, web content is inset between native bars (insetContent: true) so content never overlaps the bars. Set insetContent: false for full/under-bar scrolling.

await Navigation.setNavigationBar({ title: 'Home', insetContent: true })
await Navigation.setTabBar({ items: [...], insetContent: true })

| Mode | Behavior | |------|----------| | insetContent: true (default) | Content is bounded between bars — no overlap | | insetContent: false | Content extends under bars — bars float over content |

Platform implementation:

  • iOSscrollView.contentInset (native, rubberbanding preserved)
  • AndroidWebView.setPadding()
  • Webbody padding injected via <style> tag

Status Bar

Control the status bar icon color independently from the app color scheme — useful for full-screen hero pages or custom nav bars with dark backgrounds.

// White clock/icons (for dark backgrounds)
await Navigation.setStatusBar({ style: 'light' })

// Dark clock/icons (for light backgrounds)
await Navigation.setStatusBar({ style: 'dark' })

// Follow the active color scheme (default)
await Navigation.setStatusBar({ style: 'auto' })

// Hide status bar (iOS only)
await Navigation.setStatusBar({ hidden: true })

// Android: also set background color
await Navigation.setStatusBar({ style: 'light', backgroundColor: '#000000' })

| Platform | style: 'light' | style: 'dark' | style: 'auto' | |----------|-----------------|-----------------|-----------------| | iOS | .lightContent (white icons) | .darkContent (dark icons) | .default | | Android | isAppearanceLightStatusBars = false | = true | follows isDarkMode() |

iOS note: No Info.plist change required. The plugin installs a child view controller at runtime that takes ownership of preferredStatusBarStyle via the Objective-C runtime.


Dark / Light Mode

Dark color variants

Pass both light and dark color hex values; the native layer picks the right one automatically.

await Navigation.setTabBar({
  items: [...],
  tintColor: '#007AFF',               // light mode active
  darkTintColor: '#0A84FF',           // dark mode active
  unselectedTintColor: '#6C6C70',     // light mode inactive
  darkUnselectedTintColor: '#8E8E93', // dark mode inactive
  backgroundColor: '#F9F9F9',
  darkBackgroundColor: '#1C1C1E',
})

await Navigation.setNavigationBar({
  title: 'Home',
  tintColor: '#007AFF',
  darkTintColor: '#0A84FF',
  backgroundColor: '#F2F2F7',
  darkBackgroundColor: '#1C1C1E',
  titleColor: '#000000',
  darkTitleColor: '#FFFFFF',
})

iOS creates an adaptive UIColor(dynamicProvider:) — colors update automatically without re-calling the method. Android reads isDarkMode() at call time; re-call after colorSchemeChange to refresh.

Force dark / light from JS

// Force dark mode
await Navigation.setColorScheme({ scheme: 'dark' })

// Force light mode
await Navigation.setColorScheme({ scheme: 'light' })

// Follow system
await Navigation.setColorScheme({ scheme: 'auto' })

// Read current active scheme
const { scheme } = await Navigation.getColorScheme()
// 'light' | 'dark'

| Platform | dark | light | auto | |----------|------|-------|------| | iOS 17+ / 26+ | UIWindowScene.traitOverrides.userInterfaceStyle = .dark + JS injection | .light | .unspecified | | iOS 13–16 | UIWindow.overrideUserInterfaceStyle = .dark + JS injection | .light | .unspecified | | Android | AppCompatDelegate.MODE_NIGHT_YES | MODE_NIGHT_NO | MODE_NIGHT_FOLLOW_SYSTEM | | Web | html[data-color-scheme="dark"] + color-scheme: dark | light | removes attribute |

iOS also injects html[data-color-scheme] + html.style.colorScheme into the WKWebView so that prefers-color-scheme media queries and [data-color-scheme] CSS selectors both update reliably across all iOS versions.

React to system theme changes

await Navigation.addListener('colorSchemeChange', ({ scheme }) => {
  // scheme: 'light' | 'dark'
  // On Android: re-call setTabBar / setNavigationBar with correct colors
  // On iOS: not needed if using darkTintColor etc. (adaptive colors auto-update)
  applyTheme(scheme)
})

Platform Notes

iOS

  • Supports iosStyle: 'auto' | 'ios18' | 'ios26'
  • Supports blurStyle, backButtonStyle, and advanced back bubble tuning
  • Native edge pan emits progressive backGestureProgress

Android (14+)

  • Predictive back is enabled with native callback progress
  • Back bubble is supported and configurable through setBackGesture
  • getLayoutMetrics and layoutMetricsChange are implemented natively
  • Tab bar icons use Material icon names (androidIcon)

Option Highlights

setNavigationBar(options)

Common:

  • title, subtitle, visible
  • showBack, shadow
  • rightButton or rightButtons
  • tintColor, darkTintColor
  • backgroundColor, darkBackgroundColor
  • translucent
  • useSafeAreaTop, topInset
  • insetContent — push web content below bar (true = between bars, false = full/under)

iOS-focused:

  • iosStyle
  • blurStyle
  • backButtonStyle, backButtonLabel
  • backButtonBackgroundColor, backButtonSize, backButtonCornerRadius
  • titleColor, darkTitleColor

setTabBar(options)

  • items: TabItem[]
  • selected
  • tintColor, darkTintColor
  • unselectedTintColor, darkUnselectedTintColor
  • backgroundColor, darkBackgroundColor
  • translucent
  • useSafeAreaBottom, bottomInset
  • visible
  • insetContent — push web content above tab bar (true = between bars, false = full/under)

TabItem:

  • id, label
  • icon (SF Symbol for iOS)
  • androidIcon (Material icon name for Android)
  • badge

setBackGesture(options)

  • enabled
  • bubbleEnabled
  • bubbleTintColor
  • bubbleBackgroundColor
  • commitThreshold
  • commitVelocity
  • bubbleSize
  • bubbleRestingX
  • bubbleEmergeDistance
  • bubbleProgressDistance
  • bubbleTopInset
  • bubbleBottomInset
  • bubbleMinScale
  • bubbleMaxScale
  • bubbleMinAlpha
  • bubbleMaxAlpha
  • bubbleAllowEdgeOverflow

Vue Example (Anchor UI To Native Bars)

import { computed, onMounted, ref } from 'vue'
import { Navigation } from 'capacitor-navigation'
import type { NavigationLayoutMetrics } from 'capacitor-navigation'

const metrics = ref<NavigationLayoutMetrics | null>(null)

onMounted(async () => {
  metrics.value = await Navigation.getLayoutMetrics()
  await Navigation.addListener('layoutMetricsChange', data => {
    metrics.value = data
  })
})

const headerAccessoryStyle = computed(() => {
  if (!metrics.value) return { display: 'none' }
  return { top: `${Math.round(metrics.value.navBarBottom + 8)}px` }
})

const bottomAccessoryStyle = computed(() => {
  if (!metrics.value) return { display: 'none' }
  return { top: `${Math.round(metrics.value.tabBarTop - 56 - 10)}px` }
})

Behavior Model

  • Native bars are UI chrome only
  • Routing remains in your SPA router
  • Back events are emitted to JS; your app decides when/how to navigate