# Ionic Framework User Guide

Ionic is an open-source app development toolkit for building modern, fast, cross-platform applications from a single codebase using standard web technologies. It ships as a set of **Web Components** (custom elements such as `<ion-button>` and `<ion-modal>`) plus thin integration packages for the three major frameworks — **Angular**, **React**, and **Vue** — so you write your UI once and run it as a native mobile app, a desktop app, or a Progressive Web App (PWA).

This guide is for developers **building applications with Ionic**. It covers installation, the component catalog, theming, navigation, overlays, animations, gestures, and configuration. It documents the framework exactly as it behaves in the source at the version stamped in the footer — not what older tutorials or blog posts may claim.

> [!NOTE]
> This guide documents `@ionic/core` and the framework integration packages (`@ionic/angular`, `@ionic/react`, `@ionic/vue`) version **8.8.9**. The components are identical across all frameworks because they are all the same underlying Web Components — only the way you import and bind to them changes.

---

## 1. Quick Start

The fastest way to start is with the **Ionic CLI**, which scaffolds a complete project for your chosen framework with routing, theming, and a sample page already wired up.

> [!IMPORTANT]
> The Ionic CLI (`@ionic/cli`) is a separate tool from this framework package. It is the recommended entry point for new projects because it generates the correct project structure and installs the matching `@ionic/{angular,react,vue}` package for you.

```bash
# Install the Ionic CLI globally
npm install -g @ionic/cli
```

=== "Angular"

    ```bash
    ionic start myApp tabs --type=angular
    cd myApp
    ionic serve
    ```

=== "React"

    ```bash
    ionic start myApp tabs --type=react
    cd myApp
    ionic serve
    ```

=== "Vue"

    ```bash
    ionic start myApp tabs --type=vue
    cd myApp
    ionic serve
    ```

```text
> ionic serve
[INFO] Development server running!
       Local: http://localhost:8100
[INFO] Browser window opened to http://localhost:8100!
```

`ionic serve` opens a live-reloading development server in your browser. From here you build with the components and APIs described in the rest of this guide.

> [!TIP]
> The `tabs` argument selects a starter template. Other built-in starters include `blank`, `sidemenu`, and `list`. Run `ionic start --list` to see all available starters for each framework.

---

## 2. Installation

You can add Ionic to an existing project without the CLI by installing the framework package directly. Pick the section that matches your framework.

### 2.1 Angular

```bash
npm install @ionic/angular @ionic/angular-toolkit
```

`@ionic/angular` requires the following peer dependencies (declared in the package):

| Peer dependency | Required version |
|---|---|
| `@angular/core` | `>=16.0.0` |
| `@angular/forms` | `>=16.0.0` |
| `@angular/router` | `>=16.0.0` |
| `rxjs` | `>=7.5.0` |
| `zone.js` | `>=0.13.0` |

> [!WARNING]
> Angular 16 is the minimum supported version. Newer Angular major versions are supported as they ship, but verify compatibility before upgrading — for example, full Angular 21 support and native Vitest support are still being tracked in the issue tracker at the time this guide was generated.

### 2.2 React

```bash
npm install @ionic/react @ionic/react-router
```

`@ionic/react` is built for React applications and `@ionic/react-router` provides the routing integration layered on top of `react-router`.

### 2.3 Vue

```bash
npm install @ionic/vue @ionic/vue-router
```

`@ionic/vue` targets Vue 3. `@ionic/vue-router` wraps `vue-router` so that Ionic's page transitions and navigation stack work correctly.

### 2.4 Vanilla / Web Components (no framework)

Because Ionic is built on standard custom elements, you can use it with no framework at all by installing `@ionic/core` directly:

```bash
npm install @ionic/core
```

`@ionic/core` declares these runtime dependencies:

| Dependency | Version | Purpose |
|---|---|---|
| `@stencil/core` | `4.43.5` | Web Component runtime the components are compiled with |
| `ionicons` | `^8.0.13` | The icon set used by `<ion-icon>` |
| `tslib` | `^2.1.0` | TypeScript runtime helpers |

> [!NOTE]
> For native mobile features (camera, filesystem, geolocation, haptics, keyboard, status bar), Ionic apps use **Capacitor**. `@ionic/core` lists Capacitor 8 packages (`@capacitor/haptics`, `@capacitor/keyboard`, `@capacitor/status-bar`) as integration points. Capacitor is installed and configured separately; this guide focuses on the UI framework.

---

## 3. Core Concepts

Understanding four ideas will make everything else in this guide click into place.

### 3.1 Everything is a Web Component

Every Ionic UI element is a custom HTML element whose tag begins with `ion-`. The framework packages do not reimplement these elements — they generate lightweight wrappers that let you use the same elements idiomatically in Angular templates, JSX, or Vue templates.

```html
<ion-button color="primary">Click me</ion-button>
```

Because they are real DOM elements, they work with standard attributes, events, and CSS.

### 3.2 The App Shell: `<ion-app>`

Every Ionic application is wrapped in a single `<ion-app>` element. It is the root container that the navigation, overlays, and gesture systems anchor to. Inside it you typically place a router outlet or a menu.

```html
<ion-app>
  <ion-router-outlet></ion-router-outlet>
</ion-app>
```

### 3.3 Pages and `<ion-content>`

A "page" is a component containing a header, scrollable content, and optionally a footer:

```html
<ion-header>
  <ion-toolbar>
    <ion-title>My Page</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content class="ion-padding">
  <p>Your scrollable content goes here.</p>
</ion-content>

<ion-footer>
  <ion-toolbar>
    <ion-title>Footer</ion-title>
  </ion-toolbar>
</ion-footer>
```

`<ion-content>` provides the scroll container and is required for most layout features (refreshers, infinite scroll, fixed FABs) to work.

### 3.4 Modes: `ios` and `md`

Ionic ships **two design languages** and picks one automatically based on the device:

- **`ios`** — Apple's iOS look and feel
- **`md`** — Google's Material Design

The active mode is called the **mode** and is exposed by `getMode()`. When no mode is configured, the framework defaults to `md`. You can force a single mode app-wide through configuration (see [§13 Configuration](#13-configuration)) or per-component with the `mode` attribute:

```html
<ion-button mode="ios">Always iOS styled</ion-button>
```

> [!TIP]
> Designing with both modes in mind is what makes an Ionic app feel native on each platform. Let the default automatic detection do the work unless you have a specific reason to lock a single mode.

---

## 4. Framework Integration

The components are identical everywhere; only setup and binding differ. Read the section for your framework.

### 4.1 Angular

Ionic supports two Angular styles: the classic **NgModule** approach and the modern **standalone** approach.

**Standalone (recommended for new apps):**

```typescript
// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { provideIonicAngular } from '@ionic/angular/standalone';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent, {
  providers: [
    provideIonicAngular(),
  ],
});
```

With standalone components you import the individual Ionic components you use:

```typescript
import { Component } from '@angular/core';
import { IonButton, IonContent, IonHeader, IonTitle, IonToolbar } from '@ionic/angular/standalone';

@Component({
  selector: 'app-home',
  standalone: true,
  imports: [IonButton, IonContent, IonHeader, IonTitle, IonToolbar],
  template: `
    <ion-header><ion-toolbar><ion-title>Home</ion-title></ion-toolbar></ion-header>
    <ion-content class="ion-padding">
      <ion-button>Tap</ion-button>
    </ion-content>
  `,
})
export class HomeComponent {}
```

**NgModule (classic):**

```typescript
import { NgModule } from '@angular/core';
import { IonicModule } from '@ionic/angular';

@NgModule({
  imports: [IonicModule.forRoot()],
})
export class AppModule {}
```

Both `provideIonicAngular(config)` and `IonicModule.forRoot(config)` accept an Ionic configuration object (see [§13](#13-configuration)).

> [!IMPORTANT]
> For navigation to work, register Ionic's route reuse strategy. With standalone you add it to providers; with NgModule it is wired through `IonicRouteStrategy`:
> ```typescript
> import { RouteReuseStrategy } from '@angular/router';
> import { IonicRouteStrategy } from '@ionic/angular';
>
> providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }]
> ```

Angular also provides **controller services** (injectable equivalents of the programmatic overlay APIs): `AlertController`, `ActionSheetController`, `ModalController`, `LoadingController`, `PickerController`, `PopoverController`, `ToastController`, `MenuController`, `AnimationController`, `GestureController`, plus `Platform`, `NavController`, `Config`, and `DomController`.

### 4.2 React

Initialize Ionic once and wrap your app in `<IonApp>` and a router:

```tsx
// App.tsx
import { IonApp, IonRouterOutlet, setupIonicReact } from '@ionic/react';
import { IonReactRouter } from '@ionic/react-router';
import { Route } from 'react-router-dom';
import Home from './pages/Home';

setupIonicReact();

const App: React.FC = () => (
  <IonApp>
    <IonReactRouter>
      <IonRouterOutlet>
        <Route exact path="/home" component={Home} />
      </IonRouterOutlet>
    </IonReactRouter>
  </IonApp>
);

export default App;
```

`setupIonicReact(config)` accepts the same configuration object as the other frameworks. `@ionic/react-router` also exports `IonReactMemoryRouter` and `IonReactHashRouter` for in-memory and hash-based routing.

React components are used as JSX:

```tsx
import { IonButton, IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/react';

const Home: React.FC = () => (
  <IonPage>
    <IonHeader>
      <IonToolbar><IonTitle>Home</IonTitle></IonToolbar>
    </IonHeader>
    <IonContent className="ion-padding">
      <IonButton>Tap</IonButton>
    </IonContent>
  </IonPage>
);
```

### 4.3 Vue

Install the Ionic plugin and the router:

```typescript
// main.ts
import { createApp } from 'vue';
import { IonicVue } from '@ionic/vue';
import { createRouter, createWebHistory } from '@ionic/vue-router';
import App from './App.vue';
import { routes } from './router';

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes,
});

const app = createApp(App).use(IonicVue).use(router);

router.isReady().then(() => app.mount('#app'));
```

`app.use(IonicVue, config)` accepts the shared configuration object. `@ionic/vue-router` exports `createRouter`, `createWebHistory`, `createWebHashHistory`, and `createMemoryHistory` — these wrap the equivalents from `vue-router` so that Ionic's view stack and transitions work correctly.

Components are used in templates after importing them:

```vue
<script setup lang="ts">
import { IonButton, IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/vue';
</script>

<template>
  <ion-page>
    <ion-header><ion-toolbar><ion-title>Home</ion-title></ion-toolbar></ion-header>
    <ion-content class="ion-padding">
      <ion-button>Tap</ion-button>
    </ion-content>
  </ion-page>
</template>
```

---

## 5. Component Catalog

Ionic ships **roughly one hundred components**. The catalog below groups every component by purpose. For frequently used components you will find a working example; all components are listed in reference tables so you can discover what exists. In Angular and Vue, use the `kebab-case` tag (`<ion-button>`); in React, use the `PascalCase` JSX name (`<IonButton>`).

> [!NOTE]
> Component **properties** are set as HTML attributes (kebab-case) or JSX props (camelCase). Events use the `ion`-prefixed name (e.g. `ionChange`). The authoritative per-component property and event list is published on the official component reference; this guide documents structure, usage patterns, and the most commonly used props.

### 5.1 App Shell & Layout

| Component | Purpose |
|---|---|
| `ion-app` | Root container for the entire application (exactly one per app) |
| `ion-content` | Primary scrollable content area of a page |
| `ion-header` | Top region of a page, typically holds a toolbar |
| `ion-footer` | Bottom region of a page |
| `ion-toolbar` | Bar that holds a title and buttons; used in headers and footers |
| `ion-title` | Title text inside a toolbar |
| `ion-buttons` | Container that positions buttons within a toolbar (`slot="start"`/`"end"`) |
| `ion-grid` | Responsive 12-column flexbox grid |
| `ion-row` | A row within a grid |
| `ion-col` | A column within a row, with responsive size attributes |
| `ion-split-pane` | Two-pane layout that shows a side menu next to content on larger screens |
| `ion-backdrop` | Semi-transparent overlay behind menus and overlays |

```html
<ion-grid>
  <ion-row>
    <ion-col size="12" size-md="6">Left</ion-col>
    <ion-col size="12" size-md="6">Right</ion-col>
  </ion-row>
</ion-grid>
```

`size="12"` makes the column full width on all screens; `size-md="6"` makes it half width from the medium breakpoint up.

### 5.2 Navigation

| Component | Purpose |
|---|---|
| `ion-router-outlet` | Renders the matched route and animates page transitions |
| `ion-nav` | Imperative navigation stack (push/pop) for non-URL navigation |
| `ion-nav-link` | Declarative trigger for `ion-nav` push/pop/root navigation |
| `ion-tabs` | Tabbed navigation container |
| `ion-tab-bar` | The bar that holds tab buttons |
| `ion-tab-button` | A single button within a tab bar |
| `ion-tab` | A tab's content area |
| `ion-menu` | Slide-out side navigation menu |
| `ion-menu-button` | Button that toggles the menu |
| `ion-menu-toggle` | Wrapper that toggles the menu when its child is activated |
| `ion-back-button` | Navigates to the previous page |
| `ion-breadcrumb` / `ion-breadcrumbs` | Breadcrumb trail navigation |
| `ion-router` / `ion-route` / `ion-route-redirect` / `ion-router-link` | Vanilla (non-framework) routing elements used by `@ionic/core` |

```html
<ion-tabs>
  <ion-tab-bar slot="bottom">
    <ion-tab-button tab="home">
      <ion-icon name="home"></ion-icon>
      <ion-label>Home</ion-label>
    </ion-tab-button>
    <ion-tab-button tab="settings">
      <ion-icon name="settings"></ion-icon>
      <ion-label>Settings</ion-label>
    </ion-tab-button>
  </ion-tab-bar>
</ion-tabs>
```

> [!NOTE]
> The `ion-router`, `ion-route`, and related elements are the routing primitives for **vanilla Web Component** projects. In Angular, React, and Vue you use the framework's own router (Angular Router, React Router, Vue Router) bridged through `@ionic/angular`, `@ionic/react-router`, and `@ionic/vue-router` respectively. See [§9 Navigation & Routing](#9-navigation--routing).

### 5.3 Buttons & Actions

| Component | Purpose |
|---|---|
| `ion-button` | Standard button (`fill`, `expand`, `shape`, `size`, `color` props) |
| `ion-fab` | Floating action button container |
| `ion-fab-button` | The floating action button itself |
| `ion-fab-list` | Group of FAB buttons revealed when the main FAB is activated |
| `ion-ripple-effect` | Material ripple feedback on tap |

```html
<ion-button expand="block" fill="solid" color="primary">Submit</ion-button>
<ion-button fill="outline">Outline</ion-button>
<ion-button fill="clear" size="small">Clear</ion-button>

<ion-fab vertical="bottom" horizontal="end" slot="fixed">
  <ion-fab-button>
    <ion-icon name="add"></ion-icon>
  </ion-fab-button>
</ion-fab>
```

### 5.4 Inputs & Forms

| Component | Purpose |
|---|---|
| `ion-input` | Single-line text input (and number, email, password, etc.) |
| `ion-textarea` | Multi-line text input |
| `ion-checkbox` | Checkbox |
| `ion-radio` / `ion-radio-group` | Radio button and its group |
| `ion-toggle` | On/off switch |
| `ion-range` | Slider for selecting a value or range |
| `ion-select` / `ion-select-option` | Dropdown select and its options |
| `ion-select-modal` / `ion-select-popover` | Internal presentation surfaces for `ion-select` |
| `ion-searchbar` | Search input with clear/cancel affordances |
| `ion-datetime` | Calendar / time picker |
| `ion-datetime-button` | Compact button that opens an `ion-datetime` in an overlay |
| `ion-input-otp` | One-time-passcode (segmented) input |
| `ion-input-password-toggle` | Show/hide toggle for password inputs |
| `ion-segment` / `ion-segment-button` | Segmented control |
| `ion-segment-view` / `ion-segment-content` | Swipeable content panes tied to a segment |
| `ion-picker` / `ion-picker-column` / `ion-picker-column-option` | Wheel-style picker and columns |
| `ion-picker-legacy` / `ion-picker-legacy-column` | Deprecated legacy picker (kept for back-compat) |

```html
<ion-list>
  <ion-item>
    <ion-input label="Email" type="email" placeholder="[email removed]"></ion-input>
  </ion-item>
  <ion-item>
    <ion-toggle>Enable notifications</ion-toggle>
  </ion-item>
  <ion-item>
    <ion-select label="Fruit" placeholder="Pick one">
      <ion-select-option value="apple">Apple</ion-select-option>
      <ion-select-option value="banana">Banana</ion-select-option>
    </ion-select>
  </ion-item>
</ion-list>
```

> [!TIP]
> In Angular, Ionic form controls integrate with both template-driven and reactive forms through built-in **control value accessors** — bind with `[(ngModel)]` or `formControlName` exactly as you would with native inputs. No extra setup is required beyond importing the components.

> [!WARNING]
> Prefer `ion-datetime` and the modern `ion-picker`/`ion-picker-column` over the `ion-picker-legacy` family. The legacy picker is retained only for backward compatibility and may be removed in a future major version.

### 5.5 Lists & Items

| Component | Purpose |
|---|---|
| `ion-list` | Container for rows of items |
| `ion-list-header` | Header row for a list |
| `ion-item` | A single row; can be tappable, contain inputs, or act as a link |
| `ion-item-divider` | Visual divider/section header within a list |
| `ion-item-group` | Groups items under a divider |
| `ion-item-sliding` | Item that reveals options on swipe |
| `ion-item-options` / `ion-item-option` | The swipe-revealed option container and buttons |
| `ion-label` | Text label inside items and form controls |
| `ion-note` | Secondary/metadata text |
| `ion-reorder` / `ion-reorder-group` | Drag-to-reorder handle and group |
| `ion-avatar` | Circular image container (for profile pictures) |
| `ion-thumbnail` | Square image container |
| `ion-badge` | Small count/status indicator |
| `ion-chip` | Compact element for tags, contacts, or filters |
| `ion-icon` | Renders an icon from the Ionicons set |
| `ion-img` | Lazy-loading, viewport-aware image |

```html
<ion-list>
  <ion-item-sliding>
    <ion-item>
      <ion-avatar slot="start"><img src="avatar.png" /></ion-avatar>
      <ion-label>
        <h2>Jane Doe</h2>
        <p>Tap to view profile</p>
      </ion-label>
      <ion-badge slot="end">5</ion-badge>
    </ion-item>
    <ion-item-options side="end">
      <ion-item-option color="danger">Delete</ion-item-option>
    </ion-item-options>
  </ion-item-sliding>
</ion-list>
```

### 5.6 Cards

| Component | Purpose |
|---|---|
| `ion-card` | Card container |
| `ion-card-header` | Card header region |
| `ion-card-title` | Card title text |
| `ion-card-subtitle` | Card subtitle text |
| `ion-card-content` | Card body content |

```html
<ion-card>
  <ion-card-header>
    <ion-card-subtitle>Daily</ion-card-subtitle>
    <ion-card-title>Today's Summary</ion-card-title>
  </ion-card-header>
  <ion-card-content>
    Your tasks for today are on track.
  </ion-card-content>
</ion-card>
```

### 5.7 Overlays

These can be used declaratively (as elements) or programmatically (via controllers — see [§10](#10-overlay-controllers)).

| Component | Purpose |
|---|---|
| `ion-alert` | Dialog with a message and buttons; can collect simple input |
| `ion-action-sheet` | Sheet of actions sliding up from the bottom |
| `ion-modal` | Full or partial screen modal that can host arbitrary content |
| `ion-popover` | Contextual overlay anchored to an element |
| `ion-loading` | Blocking spinner overlay |
| `ion-toast` | Brief, non-blocking message |

```html
<ion-modal [isOpen]="isModalOpen">
  <ng-template>
    <ion-header>
      <ion-toolbar>
        <ion-title>Details</ion-title>
        <ion-buttons slot="end">
          <ion-button (click)="isModalOpen = false">Close</ion-button>
        </ion-buttons>
      </ion-toolbar>
    </ion-header>
    <ion-content class="ion-padding">Modal body</ion-content>
  </ng-template>
</ion-modal>
```

> [!TIP]
> `ion-modal` supports a **sheet modal** mode via the `breakpoints` and `initialBreakpoint` properties, letting the modal rest at partial heights (e.g. 25%, 50%, 100%) and be dragged between them.

### 5.8 Progress & Feedback

| Component | Purpose |
|---|---|
| `ion-spinner` | Animated loading indicator (multiple styles) |
| `ion-progress-bar` | Determinate or indeterminate progress bar |
| `ion-skeleton-text` | Placeholder shimmer shown while content loads |
| `ion-refresher` / `ion-refresher-content` | Pull-to-refresh control and its visuals |
| `ion-infinite-scroll` / `ion-infinite-scroll-content` | Load-more-on-scroll control and its visuals |

```html
<ion-content>
  <ion-refresher slot="fixed" (ionRefresh)="handleRefresh($event)">
    <ion-refresher-content></ion-refresher-content>
  </ion-refresher>

  <!-- list items here -->

  <ion-infinite-scroll (ionInfinite)="loadMore($event)">
    <ion-infinite-scroll-content></ion-infinite-scroll-content>
  </ion-infinite-scroll>
</ion-content>
```

### 5.9 Content & Disclosure

| Component | Purpose |
|---|---|
| `ion-accordion` / `ion-accordion-group` | Expand/collapse panels |
| `ion-text` | Inline text with color theming |
| `ion-skeleton-text` | (also listed above) loading placeholder |

```html
<ion-accordion-group>
  <ion-accordion value="first">
    <ion-item slot="header"><ion-label>Section 1</ion-label></ion-item>
    <div slot="content" class="ion-padding">Content for section 1</div>
  </ion-accordion>
  <ion-accordion value="second">
    <ion-item slot="header"><ion-label>Section 2</ion-label></ion-item>
    <div slot="content" class="ion-padding">Content for section 2</div>
  </ion-accordion>
</ion-accordion-group>
```

---

## 6. Icons

Icons come from the **Ionicons** package, a dependency of `@ionic/core`. Use the `<ion-icon>` element with a `name`:

```html
<ion-icon name="heart"></ion-icon>
<ion-icon name="logo-github" size="large"></ion-icon>
```

In React and Vue you typically import the specific icon and pass it to the `icon` prop for reliable tree-shaking and bundling:

```tsx
import { IonIcon } from '@ionic/react';
import { heart } from 'ionicons/icons';

<IonIcon icon={heart} />
```

> [!TIP]
> Importing individual icons (rather than relying on the `name` string) ensures only the icons you use are included in your bundle, keeping app size small.

---

## 7. Theming

Ionic theming is built on **CSS custom properties (variables)**. You change the look of your app by overriding variables — not by fighting the components' internal styles.

### 7.1 Global theme variables

Ionic apps include a `theme/variables.css` (or `.scss`) file. The global color palette is defined there:

```css
:root {
  --ion-color-primary: #3880ff;
  --ion-color-primary-rgb: 56, 128, 255;
  --ion-color-primary-contrast: #ffffff;
  --ion-color-primary-shade: #3171e0;
  --ion-color-primary-tint: #4c8dff;
  /* secondary, tertiary, success, warning, danger, light, medium, dark ... */
}
```

Every color has five paired variables: the base color, its `-rgb` form, a `-contrast` color for text drawn on it, and `-shade`/`-tint` variants for pressed/hover states.

### 7.2 Applying colors

Most components accept a `color` attribute referencing a palette name:

```html
<ion-button color="success">Save</ion-button>
<ion-toolbar color="primary"><ion-title>Header</ion-title></ion-toolbar>
<ion-chip color="danger">Urgent</ion-chip>
```

### 7.3 Component-level variables

Individual components expose their own CSS variables for fine-grained control:

```css
ion-button {
  --background: #6a64ff;
  --border-radius: 8px;
  --padding-top: 16px;
}
```

### 7.4 Dark mode

Ionic provides a dark palette you opt into. The common approach is to apply it based on the system preference:

```css
@media (prefers-color-scheme: dark) {
  :root {
    /* dark palette variable overrides */
  }
}
```

Ionic ships ready-made dark palette stylesheets you can import (system-driven or class-toggled), so you rarely need to hand-write the full palette.

> [!NOTE]
> Theming is intentionally CSS-first. There is no JavaScript "theme object" to configure — set CSS variables and the components respond. This keeps theming framework-agnostic and identical across Angular, React, and Vue.

---

## 8. Spacing & Utility CSS Classes

Ionic ships utility classes you can apply to any element. The most common:

| Class | Effect |
|---|---|
| `ion-padding` | Adds padding on all sides |
| `ion-padding-top` / `-bottom` / `-start` / `-end` | Directional padding |
| `ion-margin` | Adds margin on all sides |
| `ion-text-center` / `-start` / `-end` / `-justify` | Text alignment |
| `ion-text-uppercase` / `-capitalize` | Text transforms |
| `ion-float-start` / `-end` | Float helpers (direction-aware) |
| `ion-hide` | Hides an element |
| `ion-hide-{breakpoint}-{up\|down}` | Responsive show/hide |

```html
<ion-content class="ion-padding ion-text-center">
  <p class="ion-margin-top">Centered, padded content</p>
</ion-content>
```

> [!TIP]
> The `-start`/`-end` suffixes are **direction-aware**: they map to left/right based on the document's text direction (LTR vs RTL), so your layouts work in right-to-left languages automatically.

---

## 9. Navigation & Routing

Ionic adds **animated, stack-aware transitions** on top of your framework's router. The key element is `<ion-router-outlet>` (Angular/React) or the routing wrapper for your framework.

### 9.1 How Ionic routing differs

Standard web routers replace the current view. Ionic's router *outlet* keeps a **navigation stack** so that going "back" animates the previous page in from the correct direction and (where appropriate) preserves its state. This is what gives Ionic apps native-feeling forward/back transitions.

### 9.2 Per framework

=== "Angular"

    Use the Angular Router as normal, with `IonicRouteStrategy` registered (see [§4.1](#41-angular)). Place `<ion-router-outlet>` where pages render.

=== "React"

    Wrap routes in `<IonReactRouter>` (from `@ionic/react-router`) and render them inside `<IonRouterOutlet>`. Hash and memory variants are available as `IonReactHashRouter` and `IonReactMemoryRouter`.

=== "Vue"

    Use `createRouter` from `@ionic/vue-router` (it wraps `vue-router`) and render pages inside `<ion-router-outlet>`.

### 9.3 Tabs

Tabbed navigation uses `ion-tabs` containing an `ion-tab-bar` of `ion-tab-button`s, each pointing at a route. Each tab maintains its own navigation stack, so switching tabs and returning preserves where you were.

### 9.4 Programmatic navigation

In Vue, the `useIonRouter()` composable returns helpers for navigating with explicit direction and animation control. In Angular, inject `NavController`. In React, use the router hooks from `react-router` together with Ionic's lifecycle hooks.

---

## 10. Overlay Controllers

Beyond using overlays as elements, you can create them imperatively. `@ionic/core` exports a controller for each overlay type, and each framework re-exports them (Angular exposes them as injectable services):

| Controller | Creates |
|---|---|
| `alertController` | `ion-alert` |
| `actionSheetController` | `ion-action-sheet` |
| `modalController` | `ion-modal` |
| `loadingController` | `ion-loading` |
| `popoverController` | `ion-popover` |
| `toastController` | `ion-toast` |
| `pickerController` | (legacy) `ion-picker-legacy` |
| `menuController` | Controls `ion-menu` (open/close/toggle/enable) |

Every overlay controller exposes the same three methods:

| Method | Signature | Description |
|---|---|---|
| `create` | `create(options): Promise<HTMLElement>` | Builds the overlay element with the given options |
| `dismiss` | `dismiss(data?, role?, id?)` | Dismisses the top (or a specific) overlay |
| `getTop` | `getTop(): Promise<HTMLElement \| undefined>` | Returns the topmost overlay of that type |

```typescript
import { toastController } from '@ionic/core';

async function showToast() {
  const toast = await toastController.create({
    message: 'Saved successfully',
    duration: 2000,
    position: 'bottom',
  });
  await toast.present();
}
```

> [!IMPORTANT]
> `create()` only builds the overlay — you must call `.present()` on the returned element to show it. Use `await overlay.onDidDismiss()` to receive the data and role passed to `dismiss()`.

```typescript
// React / vanilla example
const alert = await alertController.create({
  header: 'Confirm',
  message: 'Delete this item?',
  buttons: [
    { text: 'Cancel', role: 'cancel' },
    { text: 'Delete', role: 'destructive' },
  ],
});
await alert.present();
const { role } = await alert.onDidDismiss();
```

The `menuController` differs slightly — it controls existing `<ion-menu>` elements:

```typescript
import { menuController } from '@ionic/core';

await menuController.open();        // open the menu
await menuController.close();       // close it
await menuController.toggle();      // toggle it
await menuController.enable(true);  // enable/disable
```

---

## 11. Animations

Ionic includes a framework-agnostic animation utility, `createAnimation`, built on the Web Animations API. It produces a chainable `Animation` object.

```typescript
import { createAnimation } from '@ionic/core';

const animation = createAnimation()
  .addElement(document.querySelector('.box')!)
  .duration(1500)
  .iterations(Infinity)
  .fromTo('transform', 'translateX(0px)', 'translateX(100px)')
  .fromTo('opacity', '1', '0.5');

await animation.play();
```

### 11.1 Key methods

| Category | Methods |
|---|---|
| **Targets** | `addElement(el)`, `addAnimation(animations)` (group child animations) |
| **Timing** | `duration(ms)`, `delay(ms)`, `iterations(n)`, `direction(dir)`, `easing(fn)`, `fill(mode)` |
| **Properties** | `from(prop, val)`, `to(prop, val)`, `fromTo(prop, from, to)`, `keyframes(frames)` |
| **Playback** | `play(opts?)`, `pause()`, `stop()`, `destroy()`, `isRunning()` |
| **Progress** | `progressStart(forceLinear?, step?)`, `progressStep(step)`, `progressEnd(playTo, step, dur?)` |
| **Hooks** | `onFinish(cb, opts?)`, `beforeStyles()`, `beforeAddClass()`, `afterStyles()`, `afterAddClass()`, `afterClearStyles()` |

> [!TIP]
> Group animations with `addAnimation()` to run several element animations as one coordinated timeline:
> ```typescript
> const parent = createAnimation().duration(1000).addAnimation([animA, animB]);
> await parent.play();
> ```

### 11.2 Replacing component transitions

Because overlay enter/leave and navigation transitions are themselves `createAnimation` builders, you can supply your own. Set `enterAnimation`/`leaveAnimation` on an overlay, or override globally via configuration keys like `modalEnter`, `modalLeave`, and `navAnimation` (see [§13](#13-configuration)).

---

## 12. Gestures

`createGesture` provides normalized, performant pointer/touch gesture handling.

```typescript
import { createGesture } from '@ionic/core';

const gesture = createGesture({
  el: document.querySelector('.draggable')!,
  gestureName: 'drag',
  direction: 'x',
  onMove: (detail) => {
    console.log('moved', detail.deltaX);
  },
});

gesture.enable();
```

### 12.1 Gesture configuration

| Option | Type | Purpose |
|---|---|---|
| `el` | `Node` | Element the gesture is attached to (required) |
| `gestureName` | `string` | Unique name (required) |
| `direction` | `'x' \| 'y'` | Axis to track |
| `threshold` | `number` | Movement before the gesture starts |
| `gesturePriority` | `number` | Higher priority wins when gestures overlap |
| `passive` | `boolean` | Whether listeners are passive |
| `maxAngle` | `number` | Maximum angle deviation allowed |
| `disableScroll` | `boolean` | Block scrolling during the gesture |
| `blurOnStart` | `boolean` | Blur the active element when the gesture starts |
| `canStart` | callback | Decide whether the gesture may begin |
| `onWillStart` | callback | Async hook before start |
| `onStart` / `onMove` / `onEnd` | callbacks | Lifecycle callbacks receiving `GestureDetail` |
| `notCaptured` | callback | Fires when the gesture is not captured |

> [!NOTE]
> The `GestureDetail` passed to callbacks includes the current/start coordinates, deltas, velocity, and the originating event — everything needed to build swipe, drag, and pinch interactions.

---

## 13. Configuration

Ionic behavior is tuned through a single configuration object of type `IonicConfig`. You provide it once at startup:

- **Angular:** `provideIonicAngular({ ... })` or `IonicModule.forRoot({ ... })`
- **React:** `setupIonicReact({ ... })`
- **Vue:** `app.use(IonicVue, { ... })`

### 13.1 Common configuration keys

| Key | Type | Effect |
|---|---|---|
| `mode` | `'ios' \| 'md'` | Force a single design mode app-wide |
| `animated` | `boolean` | Set `false` to disable all animations/transitions (useful on slow devices) |
| `rippleEffect` | `boolean` | Enable/disable Material ripple effects (default `true`) |
| `hardwareBackButton` | `boolean` | Respond to Android hardware back button (default `true` on mobile) |
| `statusTap` | `boolean` | Tapping the status bar scrolls to top (default `true` on mobile) |
| `swipeBackEnabled` | `boolean` | Enable iOS swipe-to-go-back across all `ion-nav` |
| `backButtonText` | `string` | Default text for all `ion-back-button` |
| `backButtonIcon` | `string` | Default icon for all `ion-back-button` |
| `backButtonDefaultHref` | `string` | Default fallback href for back buttons |
| `menuIcon` | `string` | Default icon for all `ion-menu-button` |
| `menuType` | `string` | Default menu animation type |
| `spinner` | `SpinnerTypes` | Default spinner for `ion-spinner` |
| `loadingSpinner` | `SpinnerTypes \| null` | Default spinner for loading overlays |
| `refreshingSpinner` | `SpinnerTypes \| null` | Default spinner for refreshers |
| `infiniteLoadingSpinner` | `SpinnerTypes \| null` | Default spinner for infinite scroll |
| `toggleOnOffLabels` | `boolean` | Default on/off labels for `ion-toggle` |
| `tabButtonLayout` | `TabButtonLayout` | Default layout of tab buttons |
| `toastDuration` | `number` | Default duration for `ion-toast` |
| `logLevel` | `'OFF' \| 'ERROR' \| 'WARN'` | Framework logging verbosity |

### 13.2 Animation override keys

You can replace any default overlay or navigation animation globally:

`navAnimation`, `actionSheetEnter`/`actionSheetLeave`, `alertEnter`/`alertLeave`, `loadingEnter`/`loadingLeave`, `modalEnter`/`modalLeave`, `popoverEnter`/`popoverLeave`, `toastEnter`/`toastLeave`, `pickerEnter`/`pickerLeave` — each takes an `AnimationBuilder`.

### 13.3 Security keys

| Key | Type | Effect |
|---|---|---|
| `sanitizerEnabled` | `boolean` | Enable Ionic's basic DOM sanitizer on properties that accept custom HTML |
| `innerHTMLTemplatesEnabled` | `boolean` | Allow custom HTML via `innerHTML` in components like `ion-alert`, `ion-toast`, `ion-loading`, `ion-infinite-scroll-content`, `ion-refresher-content`. **Disabled by default.** |

> [!CAUTION]
> `innerHTMLTemplatesEnabled` is `false` by default for security. Only enable it if you fully control and trust the HTML you pass into those components — enabling it on untrusted content opens an XSS vector. See the Admin Guide's security section.

### 13.4 Experimental keys

| Key | Type | Effect |
|---|---|---|
| `focusManagerPriority` | `('content' \| 'heading' \| 'banner')[]` | Move focus to the chosen element after each page transition (accessibility) |
| `experimentalCloseWatcher` | `boolean` | Use the CloseWatcher API for Escape/back-button dismissal (requires `hardwareBackButton: true`) |

### 13.5 Configuration via URL and persistence

Beyond code, Ionic reads configuration from the URL query string using the `ionic:` prefix, which is invaluable for testing modes without code changes:

```text
https://localhost:8100/?ionic:mode=ios&ionic:animated=false
```

Setting `persistConfig: true` saves the active configuration to `sessionStorage` (under the key `ionic-persist-config`) so URL-driven overrides survive navigation within the session.

> [!TIP]
> Use `?ionic:mode=ios` during development to instantly preview how your app looks in the other design mode without changing any code.

---

## 14. Page Lifecycle Events

Ionic pages fire lifecycle events as they enter and leave the navigation stack. These are distinct from your framework's own component lifecycle and fire on **page transitions**.

| Event | Fires |
|---|---|
| `ionViewWillEnter` | Before the page becomes the active view |
| `ionViewDidEnter` | After the page has fully transitioned in |
| `ionViewWillLeave` | Before the page transitions away |
| `ionViewDidLeave` | After the page has fully transitioned away |
| `ionViewWillUnload` | Before a page is destroyed/removed |

How you subscribe depends on the framework:

=== "Angular"

    Implement the interfaces `ViewWillEnter`, `ViewDidEnter`, `ViewWillLeave`, `ViewDidLeave` and define the matching methods (`ionViewWillEnter()`, etc.).

=== "React"

    Use the hooks exported by `@ionic/react`: `useIonViewWillEnter`, `useIonViewDidEnter`, `useIonViewWillLeave`, `useIonViewDidLeave`.

=== "Vue"

    Use the composables from `@ionic/vue`: `onIonViewWillEnter`, `onIonViewDidEnter`, `onIonViewWillLeave`, `onIonViewDidLeave`.

> [!NOTE]
> These events are what make caching navigation behave correctly: a cached page that is revisited fires `ionViewWillEnter`/`ionViewDidEnter` again without re-running its constructor, so you refresh data in these hooks rather than in the framework's "created" hook.

---

## 15. Platform Utilities

Detect the runtime environment to branch behavior:

```typescript
import { isPlatform, getPlatforms } from '@ionic/core';

if (isPlatform('ios')) {
  // iOS-specific logic
}

console.log(getPlatforms()); // e.g. ['ios', 'mobile', 'mobileweb', 'phablet']
```

`isPlatform(name)` returns `true` for platform identifiers such as `ios`, `android`, `ipad`, `iphone`, `phablet`, `tablet`, `mobile`, `desktop`, and `pwa`. `getPlatforms()` returns the full array of matching platforms. In Angular, the injectable `Platform` service offers the same checks plus observables for ready/resume/pause events.

The `mode` (`ios`/`md`) is independent of platform — `getMode()` tells you which *design language* is active, while `isPlatform()` tells you which *device* you're running on.

---

## 16. Vue & React Hooks Reference

Beyond lifecycle hooks, the framework packages export convenience hooks.

**Vue (`@ionic/vue`):**

| Hook | Returns |
|---|---|
| `useBackButton()` | Register handlers for the hardware/Escape back button |
| `useKeyboard()` | Reactive keyboard open state and height |
| `useIonRouter()` | Programmatic navigation with direction/animation control |

**React (`@ionic/react`):** the lifecycle hooks above, plus context (`NavContext`, `IonLifeCycleContext`) and `generateId` utilities used internally by routing.

---

## 17. Building for Native (Capacitor)

Ionic UI runs in a web layer; **Capacitor** packages that web app as a native iOS/Android binary and bridges to native device APIs.

```bash
npm install @capacitor/core @capacitor/cli
npx cap init
npx cap add ios
npx cap add android
npm run build          # produce your web build
npx cap sync           # copy web assets + plugins into native projects
npx cap open ios       # open in Xcode
```

> [!NOTE]
> Ionic's components automatically integrate with Capacitor plugins where relevant — for example, `@ionic/core` lists `@capacitor/keyboard`, `@capacitor/haptics`, and `@capacitor/status-bar` so that inputs adjust for the on-screen keyboard, controls give haptic feedback, and the status bar is handled correctly on device.

---

## 18. Troubleshooting

Entries below are drawn from the framework's own error messages and community-reported issues.

### Error: `framework delegate is missing`

**Cause:** An overlay that hosts user-supplied components (`ion-modal`, `ion-popover`, `ion-nav`) was used without the framework's delegate being available — usually because the Ionic provider/plugin was not initialized, or a core overlay is being used in a framework context without the wrapper.
**Solution:** Ensure you initialized Ionic for your framework (`provideIonicAngular()`/`IonicModule.forRoot()`, `setupIonicReact()`, or `app.use(IonicVue)`) and that you import the modal/popover from the framework package, not directly from `@ionic/core`.
**Prevention:** Always use the `@ionic/{angular,react,vue}` wrappers for component-hosting overlays.

### Error: `Invalid hour cycle`

**Cause:** `ion-datetime` received an `hourCycle` value it does not recognize.
**Solution:** Use a valid `hourCycle` (`h11`, `h12`, `h23`, or `h24`) or omit it to let the locale decide.
**Prevention:** Bind `hourCycle` to a constrained set of values.

### Error: `TS2320: Interface 'HTMLIonInputElement' cannot simultaneously extend types ...`

**Cause:** A TypeScript type conflict between the generated element interface and Stencil's base element interface — typically a type-version mismatch (community issue #30650).
**Solution:** Align your TypeScript and `@ionic/core` versions, clear `node_modules`, and reinstall. Ensure only one copy of `@ionic/core` types resolves.
**Prevention:** Keep `@ionic/core` and framework wrapper versions in lockstep (they share the same version number).

### Problem: `ion-datetime-button` not rendering inside a modal

**Cause:** Reported in community issues #30504 and #31155 — the datetime overlay binding can fail to associate when the button and the `ion-datetime` it controls are in different overlay contexts.
**Solution:** Ensure the `ion-datetime` referenced by the button's `datetime` property exists in the same presented overlay and that its `id` matches. Present the datetime within the same modal as the button.
**Prevention:** Keep `ion-datetime-button` and its target `ion-datetime` in the same template/overlay.

### Problem: `ion-modal` does not show content with `auto` height

**Cause:** Community issue #31149 — a modal with auto-sizing content can collapse if the content has no intrinsic height.
**Solution:** Give the modal content an explicit height or use the `breakpoints`/`initialBreakpoint` sheet-modal API instead of relying on auto height.
**Prevention:** Prefer explicit sizing for modals whose content height is dynamic.

### Problem: React Router `<Switch>` breaks passing state as child props

**Cause:** Community issue #31157 — interaction between `@ionic/react-router` and `react-router`'s `<Switch>` when passing state through child props.
**Solution:** Pass route state through route params or context rather than as child props under `<Switch>`; verify your `react-router` version matches what `@ionic/react-router` expects.
**Prevention:** Follow the routing patterns generated by `ionic start` for React.

### Warning: deprecated `host-context` pseudo-class when compiling with Lightning CSS

**Cause:** Community issue #30024 — Ionic's CSS emits `:host-context()`, which Lightning CSS flags as deprecated.
**Solution:** These are warnings, not errors; the app still works. If your build treats warnings as errors, configure your CSS pipeline to allow `:host-context`.
**Prevention:** Track the issue for a framework-side fix in a future release.

### Problem: spurious page transitions on routes with query params

**Cause:** Community issue #31152 — `IonRouter.handleHistoryChange` can compare a search-bearing URL to a search-free path, triggering extra transitions on `popstate`.
**Solution:** Be cautious mixing query-string state with Ionic navigation; where possible, encode state in path params.
**Prevention:** Minimize query-param-driven navigation within Ionic outlets until the fix lands.

---

## 19. Frequently Asked Questions

**Q: Do I have to choose Angular, React, or Vue?**
Yes — pick the framework you know. The components and APIs are identical; only the import names and binding syntax differ. You can also use `@ionic/core` directly with no framework.

**Q: Is this the same as the old `ionic-angular` package?**
No. `ionic-angular` was Ionic 3 and lives in the archived `ionic-team/ionic-v3` repository. Modern Ionic (v4+) is framework-agnostic Web Components with the `@ionic/*` packages documented here.

**Q: What is the difference between Ionic and Capacitor?**
Ionic is the UI framework (the components and APIs in this guide). Capacitor is the native runtime that wraps your web app into an iOS/Android app and exposes device APIs. They are designed to work together but are separate projects.

**Q: How do I change my app's colors?**
Override the `--ion-color-*` CSS variables in your theme file. See [§7 Theming](#7-theming).

**Q: How do I force iOS styling everywhere?**
Set `mode: 'ios'` in your Ionic configuration, or append `?ionic:mode=ios` to the URL while testing. See [§13](#13-configuration).

**Q: My animations are janky on low-end devices. What can I do?**
Set `animated: false` in configuration to disable transitions globally, or `rippleEffect: false` to drop Material ripples.

**Q: Can I use my own animations for modals and page transitions?**
Yes. Supply an `AnimationBuilder` via the per-overlay `enterAnimation`/`leaveAnimation` props or globally via config keys like `modalEnter`/`modalLeave`/`navAnimation`. See [§11.2](#112-replacing-component-transitions).

**Q: Why does my page not run its constructor when I navigate back to it?**
Ionic caches pages in the navigation stack. Use the `ionViewWillEnter`/`ionViewDidEnter` lifecycle events to refresh data on revisit. See [§14](#14-page-lifecycle-events).

**Q: How do I show a quick message?**
Use a toast — declaratively with `<ion-toast>` or programmatically with `toastController.create({ message, duration })`. See [§10](#10-overlay-controllers).

**Q: Can I use Ionic components without a build step?**
Yes — load `@ionic/core` from a CDN and use the `ion-*` elements directly in HTML. The framework wrappers exist to improve the developer experience in Angular/React/Vue, but they are optional.

**Q: How do I make my app work in right-to-left languages?**
Use Ionic's direction-aware `slot="start"`/`slot="end"` and `ion-padding-start`/`-end` utilities (rather than left/right), and set the document `dir="rtl"`. Components flip automatically.

**Q: Which Angular versions are supported?**
Angular 16 and above (peer requirement `@angular/core >=16.0.0`). Support for the newest Angular majors is added over time; check the issue tracker before adopting a brand-new Angular release.

---

_Generated June 2026 from ionic-team/ionic-framework at 3da72cd. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
