ionic-vue
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:
import { 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:
<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:
<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:
npm run build
npx cap sync
npx cap run android
npx cap run ios
For development with live reload:
ionic serve
For native live reload:
ionic 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.