
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
capacitor-navigation
Advanced tools
Native navigation bars (nav bar + tab bar) and back event bridging for Capacitor
Native navigation primitives for Capacitor apps:
This plugin is designed for SPA routers. Native bars are rendered by iOS/Android, and page transitions remain in your web layer.
pnpm add capacitor-navigation
npx cap sync
import { Navigation } from 'capacitor-navigation'
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' },
],
})
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
})
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()
})
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()backGesturebackGestureProgress with { state, progress, committed }backPressbackTaptabSelect with { id }navActionTap with { id }layoutMetricsChange with NavigationLayoutMetricscolorSchemeChange with { scheme: 'light' | 'dark' }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-topBy 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:
scrollView.contentInset (native, rubberbanding preserved)WebView.setPadding()body padding injected via <style> tagControl 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.plistchange required. The plugin installs a child view controller at runtime that takes ownership ofpreferredStatusBarStylevia the Objective-C runtime.
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 readsisDarkMode()at call time; re-call aftercolorSchemeChangeto refresh.
// 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.colorSchemeinto the WKWebView so thatprefers-color-schememedia queries and[data-color-scheme]CSS selectors both update reliably across all iOS versions.
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)
})
iosStyle: 'auto' | 'ios18' | 'ios26'blurStyle, backButtonStyle, and advanced back bubble tuningbackGestureProgresssetBackGesturegetLayoutMetrics and layoutMetricsChange are implemented nativelyandroidIcon)setNavigationBar(options)Common:
title, subtitle, visibleshowBack, shadowrightButton or rightButtonstintColor, darkTintColorbackgroundColor, darkBackgroundColortranslucentuseSafeAreaTop, topInsetinsetContent — push web content below bar (true = between bars, false = full/under)iOS-focused:
iosStyleblurStylebackButtonStyle, backButtonLabelbackButtonBackgroundColor, backButtonSize, backButtonCornerRadiustitleColor, darkTitleColorsetTabBar(options)items: TabItem[]selectedtintColor, darkTintColorunselectedTintColor, darkUnselectedTintColorbackgroundColor, darkBackgroundColortranslucentuseSafeAreaBottom, bottomInsetvisibleinsetContent — push web content above tab bar (true = between bars, false = full/under)TabItem:
id, labelicon (SF Symbol for iOS)androidIcon (Material icon name for Android)badgesetBackGesture(options)enabledbubbleEnabledbubbleTintColorbubbleBackgroundColorcommitThresholdcommitVelocitybubbleSizebubbleRestingXbubbleEmergeDistancebubbleProgressDistancebubbleTopInsetbubbleBottomInsetbubbleMinScalebubbleMaxScalebubbleMinAlphabubbleMaxAlphabubbleAllowEdgeOverflowimport { 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` }
})
FAQs
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.