Examples and videos by feature
These clips come from the NativeScript-Vue mail app in demo/, running on iOS 26 and iPadOS 26 simulators and an Android 16 emulator. Each feature's guide includes its recording beside the code. The app uses local sample messages; it does not connect to an email service.
Clips are silent, loop while visible, and have playback controls. With Reduce Motion enabled, they wait for you to press play.
Pick a feature, watch the result, and copy the corresponding snippet. Examples use NativeScript components and the demo route table. In routed pages, obtain router with useRouter() inside <script setup>; in app configuration, use the instance returned by createRouter(). Import router components from vue-router-ns. Template snippets belong in the indicated component and do not replace the initial app setup.
Installation · Transition from an element · Complete demo
Open a page from a card
The tapped element can be the source of UIKit’s native zoom. Give RouterLink a single child; Android and runtimes without the zoom API use the normal transition.
<RouterLink :to="{ name: 'message', params: { id: 1 } }" zoom>
<StackLayout padding="24" borderRadius="16" backgroundColor="#dbeafe">
<Label text="Ana" color="#1e3a8a" />
<Label text="Lunch on Friday?" color="#1e3a8a" />
</StackLayout>
</RouterLink>Complete example: card, list with zoomFrom, destination and routes. Zoom requires iOS 18+ and runtime support. In the demo, open Settings → Zoom from a card.
Tabs with independent histories
Open a message, switch tabs, and return to the same message. Tap the selected tab again to return to its root. Configure native tabs.
<RouterTabs sidebar>
<RouterTab name="inbox" title="Inbox" icon="tray.fill">
<RouterView name="inbox" initial="/" backToRootOnReselect />
</RouterTab>
<RouterTab name="settings" title="Settings" icon="gearshape.fill">
<RouterView name="settings" initial="/settings" backToRootOnReselect />
</RouterTab>
</RouterTabs>In the app root. sidebar enables the iPad sidebar; each RouterView retains its own stack. Navigating with { frame: 'settings' } also selects that tab. Full source.
Push, back and list/detail
The trail at the bottom is the live router stack. Opening another message adds an entry; Back to inbox removes the details in one navigation. A wide window keeps the list visible. Navigation · List and detail.
await router.push({ name: 'message', params: { id: 1 } })
await router.push({ name: 'message', params: { id: 2 } })
await router.backToRoot()In a wide window, replace the inbox RouterView with:
<RouterSplitView name="inbox" list="/" :breakpoint="840" :listWidth="320" />Below the breakpoint, list and detail use single-column navigation. Above it, the list stays visible while details open. Full source.
Sheets on both platforms
Compose uses a UIKit sheet with detents on iOS and a Material bottom sheet on Android. Both host a routed page with its own native navigation bar. Modals and results.
router.push('/compose', {
modal: {
ios: { detents: ['medium', 'large'], grabber: true },
android: { sheet: { state: 'expanded', height: 520 } },
},
})Tap Compose, drag the sheet, and close it with Cancel or Back. Add Material to the Android release build as described in the guide. Full source.
Preview before navigating
A long press shows a message preview and actions. Tapping the preview opens the route. Context menus.
<RouterLink to="/messages/1" :menu="{ preview: MessagePreview, props: { id: 1 } }">
<Label text="Lunch on Friday?" padding="24" />
</RouterLink>Import MessagePreview from demo/src/components/MessagePreview.vue. Previewing does not add a page to the stack; tapping the preview commits navigation. Android presents a menu without a live preview. Full source.
Guards and form restoration
Account requires a session. The guard redirects to sign-in, and typing a name activates a leave guard. On Android, that form state can also survive the operating system killing the background process. Guards · Restoration.
import { Dialogs } from '@nativescript/core'
import { ref } from 'nativescript-vue'
import { onBeforeRouteLeave, usePageState } from 'vue-router-ns'
const name = ref('')
onBeforeRouteLeave(() => Dialogs.confirm('Discard this draft?'), {
when: () => name.value.trim() !== '',
})
usePageState<{ name: string }>('form', {
save: () => ({ name: name.value }),
restore: (saved) => {
name.value = saved.name
},
})Bind name to a <TextField v-model="name" />. Persistence after process death also requires restore.save and restore.load. The authentication guard lives in demo/src/router.ts. Full source.
Native search and drawer
Search filters the current page without adding routes. The Android drawer takes destinations through the router's link policy and closes before a page is popped. Native search · Drawer.
import { computed, ref } from 'nativescript-vue'
import { useNativeSearch } from 'vue-router-ns'
const query = ref('')
const messages = [{ subject: 'Lunch on Friday?' }, { subject: 'Release notes' }]
const search = useNativeSearch({ query, placeholder: 'Search messages' })
const filtered = computed(() =>
messages.filter((message) =>
message.subject.toLowerCase().includes(query.value.toLowerCase()),
),
)Use filtered as a ListView’s :items and call search.activate() from an ActionItem. For the drawer, declare const menuOpen = ref(false) in the app root and toggle it from a button:
<RouterDrawer v-model:open="menuOpen">
<template #drawer>
<StackLayout>
<Button text="Inbox" @tap="router.push('/')" />
<Button text="Settings" @tap="router.push('/settings')" />
</StackLayout>
</template>
<RouterView />
</RouterDrawer>This minimal example has one stack. The full demo combines drawer and tabs; on iOS the content remains visible and the drawer is omitted. Full source.
Choose file conventions
The separate collection example uses generated routes, a native layout, authentication middleware and a typed modal result. Both authoring styles use the same native router. Optional setup and source.
src/pages/
index.vue → /
messages/[id].vue → /messages/:id
compose.vue → /composeChoose the webpack or Vite plugin to generate this table. The layouts and middleware examples show how to add conventions; declaring routes in TypeScript remains an independent option. Full source.
Receive a result from another page
Open Reply in a message. On send, the reply appears on the page that opened the modal.
import { ref } from 'nativescript-vue'
import { useRouter } from 'vue-router-ns'
const router = useRouter()
const reply = ref<string>()
async function compose() {
reply.value = await router.pushForResult<string>('/compose')
}In Compose, call router.back({ result: text.value }). Closing without sending resolves with undefined. The type describes the contract between pages. Guide · Full source.
Open a link in the correct tab
In app configuration, use the same stack names as RouterTabs.
const router = createRouter({
routes,
links: {
schemes: ['https', 'vuerouterns'],
hosts: ['vue-router-ns.rafaelaugusto.dev'],
frame: (to) => (to.path.startsWith('/settings') ? 'settings' : 'inbox'),
},
})
await router.openLink('vuerouterns://messages/1')Also try vuerouterns://settings/account while signed out: the guard should open login in Settings. OS URL delivery requires native scheme/domain association configuration; openLink() tests dispatch within the app. Setup and notification examples.
Shortcuts on the app icon
Publish destinations after the app is ready, as in demo/src/app.ts.
router.setShortcuts([
{
id: 'compose',
title: 'New message',
to: '/compose',
icon: { ios: 'square.and.pencil' },
},
{ id: 'settings', title: 'Settings', to: '/settings' },
])Long-press the app icon in the launcher/SpringBoard and choose New message. The route uses the same guards. Use setShortcuts([]) to remove dynamic shortcuts on sign-out. Limits and pinned shortcuts.
Attach a system document
In a routed page’s setup, prepare the picker and call attach() from an Attach button.
import { ref } from 'nativescript-vue'
import { createDocumentPicker, useNativeFlow } from 'vue-router-ns'
const attachments = ref<string[]>([])
const picker = useNativeFlow(createDocumentPicker())
async function attach() {
const outcome = await picker.run({ types: ['application/pdf'], multiple: true })
if (outcome.status === 'completed') attachments.value = outcome.value.uris
else if (outcome.status === 'failed') console.error(outcome.error)
}Choose a PDF or cancel. The page stays at the same history entry; cancellation retains the previous selection. URIs do not guarantee permanent permission: reading and persisting access belong to the app. Complete picker and lifecycle.
Return focus to the button that opened a modal
With VoiceOver or TalkBack enabled, open and close Reply. Focus should return to the initiating button.
import { useNavigationFocus, useRouter } from 'vue-router-ns'
const router = useRouter()
const focus = useNavigationFocus({ initial: 'heading', return: 'reply' })
async function reply() {
focus.remember('reply')
await router.pushForResult('/compose')
}<Label id="heading" text="Message" accessible="true" />
<Button id="reply" text="Reply" @tap="reply" />Place the controls inside the page. This is accessibility focus, not keyboard focus. The default motion: 'system' respects reduced motion. Complete example and limits.
Build a stack with a predictable way back
Use a list of destinations when an entry point needs a specific history.
await router.setStack(['/', '/messages/1', '/messages/2'])
await router.back()
await router.backToRoot({ animated: false })After setStack, Back opens message 1; the last command returns to the inbox without animation. Guards can interrupt the sequence, and completed steps remain. Setting the stack.
More complete examples
- Keyboard commands: Cmd/Ctrl, Escape and page scopes.
- Typed routes: names and parameters checked by TypeScript.
- Native destinations: integrate UIViewController and Fragment (experimental).
- Predictive back: Android gesture previews and cancellation.
- Diagnostics: observe failures, transitions and history.
The videos are recordings of native navigation. The focus example includes VoiceOver/TalkBack steps: a silent video does not demonstrate screen-reader announcements.
Run the same app
Install the NativeScript CLI, then build the local router from the repository root:
npm install
npm run build
cd demo
npm install
ns run ios
# or: ns run androidThe demo resolves vue-router-ns from the local dist directory. Use npm run dev in a separate terminal while changing the router. See the demo README for the APIs used by each page.