Ionic Vue
Develop Ionic apps with Vue — project structure, components, navigation, lifecycle hooks, composables, and Vue-specific patterns.
Prerequisites
- Ionic Framework 7 or 8 with
@ionic/vue. - Vue 3.
- Node.js and npm installed.
- For iOS: Xcode installed.
- For Android: Android Studio installed.
Agent Behavior
- Auto-detect before asking. Check the project for
package.jsondependencies (@ionic/vue,vue,@capacitor/core), platforms (android/,ios/), build tools (vite.config.ts), and TypeScript usage. Only ask the user when something cannot be detected. - Guide step-by-step. Walk the user through the process one step at a time. Never present multiple unrelated questions at once.
- Adapt to the project. Detect whether the project uses
<script setup>(Composition API) or Options API and generate code that matches the existing style. Default to<script setup lang="ts">for new files unless the project uses a different pattern.
Procedures
Step 1: Analyze the Project
Auto-detect the following by reading project files:
- Ionic version: Read
@ionic/vueversion frompackage.json. - Vue version: Read
vueversion frompackage.json. - Capacitor version: Read
@capacitor/coreversion frompackage.json(if present). - TypeScript: Check if
tsconfig.jsonexists and if.vuefiles use<script setup lang="ts">. - Platforms: Check which directories exist (
android/,ios/). - Router config: Locate the router file (typically
src/router/index.ts). - Project template: Determine if the project uses tabs, side-menu, or blank template by examining the router configuration and
App.vue.
Step 2: Project Structure
A standard Ionic Vue project follows this structure:
project-root/ ├── android/ # Android native project (generated by Capacitor) ├── ios/ # iOS native project (generated by Capacitor) ├── public/ ├── src/ │ ├── components/ # Reusable Vue components │ ├── composables/ # Custom composables │ ├── router/ │ │ └── index.ts # Vue Router configuration (uses @ionic/vue-router) │ ├── theme/ │ │ └── variables.css # Ionic CSS custom properties │ ├── views/ # Page components (routed views) │ ├── App.vue # Root component (contains IonApp + IonRouterOutlet) │ └── main.ts # Entry point (installs IonicVue plugin) ├── capacitor.config.ts # Capacitor configuration ├── package.json ├── tsconfig.json └── vite.config.ts
If the project does not follow this structure, adapt all guidance to the project's actual directory layout. Do not restructure the project unless the user explicitly asks.
Step 3: App Entry Point and Root Component
The entry point src/main.ts installs the IonicVue plugin and mounts the app:
typescriptimport { createApp } from 'vue'; import { IonicVue } from '@ionic/vue'; import App from './App.vue'; import router from './router'; /* Ionic CSS */ import '@ionic/vue/css/core.css'; import '@ionic/vue/css/normalize.css'; import '@ionic/vue/css/structure.css'; import '@ionic/vue/css/typography.css'; import '@ionic/vue/css/padding.css'; import '@ionic/vue/css/float-elements.css'; import '@ionic/vue/css/text-alignment.css'; import '@ionic/vue/css/text-transformation.css'; import '@ionic/vue/css/flex-utils.css'; import '@ionic/vue/css/display.css'; /* Theme */ import './theme/variables.css'; const app = createApp(App).use(IonicVue).use(router); router.isReady().then(() => { app.mount('#app'); });
The root App.vue contains IonApp and IonRouterOutlet:
vue<template> <ion-app> <ion-router-outlet /> </ion-app> </template> <script setup lang="ts"> import { IonApp, IonRouterOutlet } from '@ionic/vue'; </script>
Step 4: Components and IonPage
Read references/components.md for detailed component patterns.
Key rules:
- Import all Ionic components from
@ionic/vue— e.g.,import { IonButton, IonContent, IonPage } from '@ionic/vue'. - Every routed page must use
IonPageas its root template element. Without it, page transitions break and Ionic lifecycle hooks do not fire. - Access Web Component methods via
$el— e.g.,contentRef.value.$el.scrollToBottom(300). - Import icons as SVG references from
ionicons/icons— never pass icon names as strings. - Use
v-modelon Ionic form components (IonInput,IonToggle,IonSelect,IonCheckbox,IonRange). - Use kebab-case for Ionic event names in templates — e.g.,
@ion-change,@ion-infinite.
Step 5: Navigation and Routing
Read references/navigation.md for detailed routing patterns.
Key principles:
- Import
createRouterfrom@ionic/vue-router, not fromvue-router. The Ionic version wraps Vue Router to enable page transitions. - Declarative navigation: Use
router-linkattribute on Ionic components with optionalrouter-directionandrouter-animation. - Programmatic navigation: Use the
useIonRoutercomposable for Ionic-specific transitions, oruseRouterfromvue-routerfor standard navigation. - Lazy load routes with
component: () => import('@/views/DetailPage.vue'). - Tab routing: Use nested routes with
IonTabsandIonRouterOutlet. Each tab maintains its own navigation stack. - Never cross-route between tabs — only tab bar buttons should switch tabs. Use
IonModalfor views shared across tabs.
Step 6: Lifecycle Hooks
Read references/lifecycle.md for detailed lifecycle patterns.
Key principles:
IonRouterOutletkeeps pages in the DOM. Vue'sonMountedfires only once per page, not on every visit.- Use Ionic lifecycle hooks to run logic on every page visit:
onIonViewWillEnter— Refresh data every time the page appears.onIonViewDidEnter— Start animations or focus inputs after the page transition finishes.onIonViewWillLeave— Save state or unsubscribe.onIonViewDidLeave— Clean up off-screen resources.
- Ionic lifecycle hooks require
IonPageas the root element. - Only router-mapped components receive lifecycle hooks — child components do not.
Step 7: Composables and Utilities
Read references/composables.md for detailed composable documentation.
Available composables and utilities from @ionic/vue:
| Function | Purpose |
|---|---|
useIonRouter() | Programmatic navigation with transition control |
useBackButton(priority, handler) | Handle Android hardware back button |
useKeyboard() | Reactive keyboard visibility and height |
onIonViewWillEnter(cb) | Lifecycle: page about to show |
onIonViewDidEnter(cb) | Lifecycle: page fully visible |
onIonViewWillLeave(cb) | Lifecycle: page about to hide |
onIonViewDidLeave(cb) | Lifecycle: page fully hidden |
isPlatform(name) | Check current platform (ios, android, hybrid, etc.) |
getPlatforms() | Get array of all matching platform identifiers |
Step 8: Platform Detection
Use isPlatform from @ionic/vue to conditionally execute platform-specific logic:
vue<script setup lang="ts"> import { isPlatform } from '@ionic/vue'; const isIOS = isPlatform('ios'); const isAndroid = isPlatform('android'); const isNative = isPlatform('hybrid'); const isMobileWeb = isPlatform('mobileweb'); </script> <template> <div> <p v-if="isNative">Running as a native app</p> <p v-else>Running in a browser</p> </div> </template>
Supported identifiers: android, capacitor, cordova, desktop, electron, hybrid, ios, ipad, iphone, mobile, mobileweb, phablet, pwa, tablet.
Step 9: Build and Run
After implementing changes:
bashnpm run build npx cap sync npx cap run android npx cap run ios
For development with live reload:
bashionic serve
For native live reload:
bashionic cap run android --livereload --external ionic cap run ios --livereload --external
Error Handling
Failed to resolve component: ion-*: The Ionic component is not imported. Add the missing import from@ionic/vuein the<script>section (e.g.,import { IonButton } from '@ionic/vue').- Page transitions not working: Verify that the page component uses
IonPageas its root template element. WithoutIonPage, transitions and lifecycle hooks silently fail. - Ionic lifecycle hooks not firing: Ensure
IonPageis the root element and the component is directly mapped to a route in the router configuration. Lifecycle hooks do not fire on child components. Method on component is not a function: Access the underlying Web Component via$el— e.g.,ref.value.$el.scrollToBottom(), notref.value.scrollToBottom().- Slot attribute deprecation warning: Ionic uses Web Component slots, which linters misidentify as deprecated Vue 2 slots. Disable the ESLint rule:
'vue/no-deprecated-slot-attribute': 'off'. - Event listeners not firing: Use kebab-case event names in templates (
ion-modal-did-present) and inaddEventListenercalls. Do not use camelCase. useIonRouteroruseBackButtonfails: These composables use Vue'sinject()internally and must be called insidesetup()or<script setup>, not in standalone functions or callbacks.- Tab navigation loses state: Each tab maintains its own stack. Do not use
router.go()with non-linear (tab-based) routing. UseuseIonRouter().push()orrouter-linkinstead. - Vue
onMountedfires only once:IonRouterOutletcaches pages in the DOM. UseonIonViewWillEnterinstead ofonMountedfor logic that must run every time the page is shown. - Content overlaps tab bar or header: The page component is missing
IonPageas the root element.IonPagesets up the proper flexbox layout.
Related Skills
ionic-app-development— General Ionic development guidance: components, theming, CLI usage.ionic-app-creation— Create a new Ionic app from scratch.capacitor-vue— Capacitor-specific Vue patterns (plugins, hooks, services) without Ionic Framework.ionic-app-upgrades— Upgrade Ionic Framework to a newer version.


