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/coreand 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.
# Install the Ionic CLI globally
npm install -g @ionic/cli
Angular
ionic start myApp tabs --type=angular
cd myApp
ionic serve
React
ionic start myApp tabs --type=react
cd myApp
ionic serve
Vue
ionic start myApp tabs --type=vue
cd myApp
ionic serve
> 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
tabsargument selects a starter template. Other built-in starters includeblank,sidemenu, andlist. Runionic start --listto 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
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
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
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:
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/corelists 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.
<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.
<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:
<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 feelmd— 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) or per-component with the mode attribute:
<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):
// 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:
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):
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).
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: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:
// 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:
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:
// 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:
<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 |
<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 |
<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-routerrespectively. See §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 |
<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) |
<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)]orformControlNameexactly as you would with native inputs. No extra setup is required beyond importing the components.
Warning
Prefer
ion-datetimeand the modernion-picker/ion-picker-columnover theion-picker-legacyfamily. 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 |
<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 |
<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).
| 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 |
<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-modalsupports a sheet modal mode via thebreakpointsandinitialBreakpointproperties, 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 |
<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 |
<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:
<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:
import { IonIcon } from '@ionic/react';
import { heart } from 'ionicons/icons';
<IonIcon icon={heart} />
Tip
Importing individual icons (rather than relying on the
namestring) 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:
: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:
<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:
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:
@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 |
<ion-content class="ion-padding ion-text-center">
<p class="ion-margin-top">Centered, padded content</p>
</ion-content>
Tip
The
-start/-endsuffixes 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). 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-buttons, 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 |
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. Useawait overlay.onDidDismiss()to receive the data and role passed todismiss().
// 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:
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.
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: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).
12. Gestures
createGesture provides normalized, performant pointer/touch gesture handling.
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
GestureDetailpassed 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({ ... })orIonicModule.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
innerHTMLTemplatesEnabledisfalseby 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:
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=iosduring 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/ionViewDidEnteragain 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:
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.
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/corelists@capacitor/keyboard,@capacitor/haptics, and@capacitor/status-barso 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.
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.
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.
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.
Q: How do I show a quick message?
Use a toast — declaratively with <ion-toast> or programmatically with toastController.create({ message, duration }). See §10.
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.