---
name: ionic-vue
description: "Guides the agent through Ionic Vue development patterns — project structure, Vue-specific Ionic components (IonPage, IonRouterOutlet, IonTabs), navigation with Vue Router and useIonRouter, Ionic lifecycle hooks (onIonViewWillEnter, onIonViewDidEnter, onIonViewWillLeave, onIonViewDidLeave), composable utilities (useIonRouter, useBackButton, useKeyboard), tab-based routing, lazy loading, platform detection with isPlatform, and troubleshooting common Vue-specific issues. Do not use for general Ionic component theming or CLI usage (use ionic-app-development), creating a new Ionic app (use ionic-app-creation), Capacitor-specific Vue patterns without Ionic (use capacitor-vue), upgrading Ionic versions (use ionic-app-upgrades), or non-Vue frameworks like Angular or React."
metadata:
  author: capawesome-team
  source: https://github.com/capawesome-team/skills/tree/main/skills/ionic-vue
---

# Ionic Vue

Develop Ionic apps with Vue — project structure, components, navigation, lifecycle hooks, composables, and Vue-specific patterns.

## Prerequisites

1. **Ionic Framework 7 or 8** with `@ionic/vue`.
2. **Vue 3**.
3. Node.js and npm installed.
4. For **iOS**: Xcode installed.
5. For **Android**: Android Studio installed.

## Agent Behavior

- **Auto-detect before asking.** Check the project for `package.json` dependencies (`@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:

1. **Ionic version**: Read `@ionic/vue` version from `package.json`.
2. **Vue version**: Read `vue` version from `package.json`.
3. **Capacitor version**: Read `@capacitor/core` version from `package.json` (if present).
4. **TypeScript**: Check if `tsconfig.json` exists and if `.vue` files use `<script setup lang="ts">`.
5. **Platforms**: Check which directories exist (`android/`, `ios/`).
6. **Router config**: Locate the router file (typically `src/router/index.ts`).
7. **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:

```typescript
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`:

```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:

1. **Import all Ionic components** from `@ionic/vue` — e.g., `import { IonButton, IonContent, IonPage } from '@ionic/vue'`.
2. **Every routed page must use `IonPage`** as its root template element. Without it, page transitions break and Ionic lifecycle hooks do not fire.
3. **Access Web Component methods via `$el`** — e.g., `contentRef.value.$el.scrollToBottom(300)`.
4. **Import icons as SVG references** from `ionicons/icons` — never pass icon names as strings.
5. **Use `v-model`** on Ionic form components (`IonInput`, `IonToggle`, `IonSelect`, `IonCheckbox`, `IonRange`).
6. **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:

1. **Import `createRouter` from `@ionic/vue-router`**, not from `vue-router`. The Ionic version wraps Vue Router to enable page transitions.
2. **Declarative navigation**: Use `router-link` attribute on Ionic components with optional `router-direction` and `router-animation`.
3. **Programmatic navigation**: Use the `useIonRouter` composable for Ionic-specific transitions, or `useRouter` from `vue-router` for standard navigation.
4. **Lazy load routes** with `component: () => import('@/views/DetailPage.vue')`.
5. **Tab routing**: Use nested routes with `IonTabs` and `IonRouterOutlet`. Each tab maintains its own navigation stack.
6. **Never cross-route between tabs** — only tab bar buttons should switch tabs. Use `IonModal` for views shared across tabs.

### Step 6: Lifecycle Hooks

Read `references/lifecycle.md` for detailed lifecycle patterns.

Key principles:

1. `IonRouterOutlet` keeps pages in the DOM. Vue's `onMounted` fires only once per page, not on every visit.
2. 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.
3. Ionic lifecycle hooks require `IonPage` as the root element.
4. 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:

```bash
npm run build
npx cap sync
npx cap run android
npx cap run ios
```

For development with live reload:

```bash
ionic serve
```

For native live reload:

```bash
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/vue` in the `<script>` section (e.g., `import { IonButton } from '@ionic/vue'`).
- **Page transitions not working**: Verify that the page component uses `IonPage` as its root template element. Without `IonPage`, transitions and lifecycle hooks silently fail.
- **Ionic lifecycle hooks not firing**: Ensure `IonPage` is 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()`, not `ref.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 in `addEventListener` calls. Do not use camelCase.
- **`useIonRouter` or `useBackButton` fails**: These composables use Vue's `inject()` internally and must be called inside `setup()` 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. Use `useIonRouter().push()` or `router-link` instead.
- **Vue `onMounted` fires only once**: `IonRouterOutlet` caches pages in the DOM. Use `onIonViewWillEnter` instead of `onMounted` for logic that must run every time the page is shown.
- **Content overlaps tab bar or header**: The page component is missing `IonPage` as the root element. `IonPage` sets 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.
