ion-modal
Contents
A Modal is a dialog that appears on top of the app's content, and must be dismissed by the app before interaction can resume. It is useful as a select component when there are a lot of options to choose from, or when filtering items in a list, as well as many other use cases.
Presenting
There are two ways to use ion-modal
: inline or via the modalController
. Each method comes with different considerations, so be sure to use the approach that best fits your use case.
Inline Modals
ion-modal
can be used by writing the component directly in your template. This reduces the number of handlers you need to wire up in order to present the modal. See Usage for an example of how to write a modal inline.
When using ion-modal
with Angular, React, or Vue, the component you pass in will be destroyed when the modal is dismissed. As this functionality is provided by the JavaScript framework, using ion-modal
without a JavaScript framework will not destroy the component you passed in. If this is a needed functionality, we recommend using the modalController
instead.
Angular
Since the component you passed in needs to be created when the modal is presented and destroyed when the modal is dismissed, we are unable to project the content using <ng-content>
internally. Instead, we use <ng-container>
which expects an <ng-template>
to be passed in. As a result, when passing in your component you will need to wrap it in an <ng-template>
:
<ion-modal [isOpen]="isModalOpen">
<ng-template>
<app-modal-content></app-modal-content>
</ng-template>
</ion-modal>
When to use
Using a modal inline is useful when you do not want to explicitly wire up click events to open the modal. For example, you can use the is-open
property to easily present or dismiss a modal based on some state in your application.
If you need fine grained control over when the modal is presented and dismissed, we recommend you use the modalController
.
Controller Modals
ion-modal
can also be presented programmatically by using the modalController
imported from Ionic Framework. This allows you to have complete control over when a modal is presented above and beyond the customization that inline modals give you. See Usage for an example of how to use the modalController
.
When to use
We typically recommend that you write your modals inline as it streamlines the amount of code in your application. You should only use the modalController
for complex use cases where writing a modal inline is impractical.
Card Modal
Developers can create a card modal effect where the modal appears as a card stacked on top of your app's main content. To create a card modal, developers need to set the presentingElement
property and the swipeToClose
properties on ion-modal
.
The presentingElement
property accepts a reference to the element that should display under your modal. This is typically a reference to ion-router-outlet
.
The swipeToClose
property can be used to control whether or not the card modal can be swiped to close.
See Usage for examples on how to use the sheet modal.
Sheet Modal
Developers can create a sheet modal effect similar to the drawer components available in maps applications. To create a sheet modal, developers need to set the breakpoints
and initialBreakpoint
properties on ion-modal
.
The breakpoints
property accepts an array which states each breakpoint that the sheet can snap to when swiped. A breakpoints
property of [0, 0.5, 1]
would indicate that the sheet can be swiped to show 0% of the modal, 50% of the modal, and 100% of the modal. When the modal is swiped to 0%, the modal will be automatically dismissed.
The initialBreakpoint
property is required so that the sheet modal knows which breakpoint to start at when presenting. The initalBreakpoint
value must also exist in the breakpoints
array. Given a breakpoints
value of [0, 0.5, 1]
, an initialBreakpoint
value of 0.5
would be valid as 0.5
is in the breakpoints
array. An initialBreakpoint
value of 0.25
would not be valid as 0.25
does not exist in the breakpoints
array.
The backdropBreakpoint
property can be used to customize the point at which the ion-backdrop
will begin to fade in. This is useful when creating interfaces that have content underneath the sheet that should remain interactive. A common use case is a sheet modal that overlays a map where the map is interactive until the sheet is fully expanded.
See Usage for examples on how to use the sheet modal.
note
Note: The swipeToClose
property has no effect when using a sheet modal as sheet modals must be swipeable in order to be usable.
Interfaces
Below you will find all of the options available to you when using the modalController
. These options should be supplied when calling modalController.create()
.
interface ModalOptions {
component: any;
componentProps?: { [key: string]: any };
presentingElement?: HTMLElement;
showBackdrop?: boolean;
backdropDismiss?: boolean;
cssClass?: string | string[];
animated?: boolean;
swipeToClose?: boolean;
mode?: 'ios' | 'md';
keyboardClose?: boolean;
id?: string;
enterAnimation?: AnimationBuilder;
leaveAnimation?: AnimationBuilder;
}
Dismissing
The modal can be dismissed after creation by calling the dismiss()
method on the modal controller. The onDidDismiss
function can be called to perform an action after the modal is dismissed.
Styling
Modals are presented at the root of your application so they overlay your entire app. This behavior applies to both inline modals and modals presented from a controller. As a result, custom modal styles can not be scoped to a particular component as they will not apply to the modal. Instead, styles must be applied globally. For most developers, placing the custom styles in global.css
is sufficient.
note
If you are building an Ionic Angular app, the styles need to be added to a global stylesheet file. Read Style Placement in the Angular section below for more information.
note
ion-modal
works under the assumption that stacked modals are the same size. As a result, each subsequent modal will have no box shadow and a backdrop opacity of 0
. This is to avoid the effect of shadows and backdrops getting darker with each added modal. This can be changed by setting the --box-shadow
and --backdrop-opacity
CSS variables:
ion-modal.stack-modal {
--box-shadow: 0 28px 48px rgba(0, 0, 0, 0.4);
--backdrop-opacity: var(--ion-backdrop-opacity, 0.32);
}
Interfaces
ModalOptions
interface ModalOptions<T extends ComponentRef = ComponentRef> {
component: T;
componentProps?: ComponentProps<T>;
presentingElement?: HTMLElement;
showBackdrop?: boolean;
backdropDismiss?: boolean;
cssClass?: string | string[];
animated?: boolean;
swipeToClose?: boolean;
mode?: Mode;
keyboardClose?: boolean;
id?: string;
htmlAttributes?: ModalAttributes;
enterAnimation?: AnimationBuilder;
leaveAnimation?: AnimationBuilder;
breakpoints?: number[];
initialBreakpoint?: number;
backdropBreakpoint?: number;
handle?: boolean;
}
ModalAttributes
interface ModalAttributes extends JSXBase.HTMLAttributes<HTMLElement> {}
Usage
- Angular
- Javascript
- React
- Stencil
- Vue
Inline Modal
<!-- Default -->
<ion-modal [isOpen]="true">
<ng-template>
<ion-content>Modal Content</ion-content>
</ng-template>
</ion-modal>
<!-- Use a trigger -->
<ion-button id="trigger-button">Click to open modal</ion-button>
<ion-modal trigger="trigger-button">
<ng-template>
<ion-content>Modal Content</ion-content>
</ng-template>
</ion-modal>
<!-- Sheet Modal -->
<ion-modal
[isOpen]="true"
[breakpoints]="[0.1, 0.5, 1]"
[initialBreakpoint]="0.5"
>
<ng-template>
<ion-content>Modal Content</ion-content>
</ng-template>
</ion-modal>
<!-- Card Modal -->
<ion-modal
[isOpen]="true"
[swipeToClose]="true"
[presentingElement]="routerOutlet.nativeEl"
>
<ng-template>
<ion-content>Modal Content</ion-content>
</ng-template>
</ion-modal>
<!-- Passing Props -->
<ion-modal [isOpen]="true">
<ng-template>
<app-angular-component title="Ionic"></app-angular-component>
</ng-template>
</ion-modal>
import { Component } from '@angular/core';
import { IonRouterOutlet } from '@ionic/angular';
@Component({
selector: 'modal-example',
templateUrl: 'modal-example.html',
styleUrls: ['./modal-example.css']
})
export class ModalExample {
constructor(public routerOutlet: IonRouterOutlet) {}
}
Modal Controller
import { Component } from '@angular/core';
import { ModalController } from '@ionic/angular';
import { ModalPage } from '../modal/modal.page';
@Component({
selector: 'modal-example',
templateUrl: 'modal-example.html',
styleUrls: ['./modal-example.css']
})
export class ModalExample {
constructor(public modalController: ModalController) {
}
async presentModal() {
const modal = await this.modalController.create({
component: ModalPage,
cssClass: 'my-custom-class'
});
return await modal.present();
}
}
import { Component, Input } from '@angular/core';
@Component({
selector: 'modal-page',
})
export class ModalPage {
constructor() {}
}
If you need a wrapper element inside of your modal component, we recommend using a
<div class="ion-page">
so that the component dimensions are still computed properly.
Passing Data
During creation of a modal, data can be passed in through the componentProps
.
The previous example can be written to include data:
async presentModal() {
const modal = await this.modalController.create({
component: ModalPage,
cssClass: 'my-custom-class',
componentProps: {
'firstName': 'Douglas',
'lastName': 'Adams',
'middleInitial': 'N'
}
});
return await modal.present();
}
To get the data passed into the componentProps
, set it as an @Input
:
export class ModalPage {
// Data passed in by componentProps
@Input() firstName: string;
@Input() lastName: string;
@Input() middleInitial: string;
}
Dismissing a Modal
A modal can be dismissed by calling the dismiss method on the modal controller and optionally passing any data from the modal.
export class ModalPage {
...
dismiss() {
// using the injected ModalController this page
// can "dismiss" itself and optionally pass back data
this.modalController.dismiss({
'dismissed': true
});
}
}
After being dismissed, the data can be read in through the onWillDismiss
or onDidDismiss
attached to the modal after creation:
const { data } = await modal.onWillDismiss();
console.log(data);
Lazy Loading
When lazy loading a modal, it's important to note that the modal will not be loaded when it is opened, but rather when the module that imports the modal's module is loaded.
For example, say there exists a CalendarComponent
and an EventModal
. The modal is presented by clicking a button in the CalendarComponent
. In Angular, the EventModalModule
would need to be included in the CalendarComponentModule
since the modal is created in the CalendarComponent
:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IonicModule } from '@ionic/angular';
import { CalendarComponent } from './calendar.component';
import { EventModalModule } from '../modals/event/event.module';
@NgModule({
declarations: [
CalendarComponent
],
imports: [
IonicModule,
CommonModule,
EventModalModule
],
exports: [
CalendarComponent
]
})
export class CalendarComponentModule {}
Card Modals
Modals in iOS mode have the ability to be presented in a card-style and swiped to close. The card-style presentation and swipe to close gesture are not mutually exclusive, meaning you can pick and choose which features you want to use. For example, you can have a card-style modal that cannot be swiped or a full sized modal that can be swiped.
Card style modals when running on iPhone-sized devices do not have backdrops. As a result, the
--backdrop-opacity
variable will not have any effect.
If you are creating an application that uses ion-tabs
, it is recommended that you get the parent ion-router-outlet
using this.routerOutlet.parentOutlet.nativeEl
, otherwise the tabbar will not scale down when the modal opens.
import { IonRouterOutlet } from '@ionic/angular';
constructor(private routerOutlet: IonRouterOutlet) {}
async presentModal() {
const modal = await this.modalController.create({
component: ModalPage,
cssClass: 'my-custom-class',
swipeToClose: true,
presentingElement: this.routerOutlet.nativeEl
});
return await modal.present();
}
In most scenarios, using the ion-router-outlet
element as the presentingElement
is fine. In cases where you are presenting a card-style modal from within another modal, you should pass in the top-most ion-modal
element as the presentingElement
.
import { ModalController } from '@ionic/angular';
constructor(private modalController: ModalController) {}
async presentModal() {
const modal = await this.modalController.create({
component: ModalPage,
cssClass: 'my-custom-class',
swipeToClose: true,
presentingElement: await this.modalController.getTop() // Get the top-most ion-modal
});
return await modal.present();
}
Sheet Modals
import { IonRouterOutlet } from '@ionic/angular';
constructor(private routerOutlet: IonRouterOutlet) {}
async presentModal() {
const modal = await this.modalController.create({
component: ModalPage,
initialBreakpoint: 0.5,
breakpoints: [0, 0.5, 1]
});
return await modal.present();
}
Style Placement
In Angular, the CSS of a specific page is scoped only to elements of that page. Even though the Modal can be presented from within a page, the ion-modal
element is appended outside of the current page. This means that any custom styles need to go in a global stylesheet file. In an Ionic Angular starter this can be the src/global.scss
file or you can register a new global style file by adding to the styles
build option in angular.json
.
Inline Modal
<!-- Default -->
<ion-modal is-open="true">
<ion-content>Modal Content</ion-content>
</ion-modal>
<!-- Use a trigger -->
<ion-button id="trigger-button">Click to open modal</ion-button>
<ion-modal trigger="trigger-button">
<ion-content>Modal Content</ion-content>
</ion-modal>
<!-- Sheet Modal -->
<ion-modal is-open="true" id="sheet-modal">
<ion-content>Modal Content</ion-content>
</ion-modal>
<!-- Card Modal -->
<ion-modal is-open="true" id="card-modal">
<ion-content>Modal Content</ion-content>
</ion-modal>
<script>
const sheetModal = document.querySelector('#sheet-modal');
const cardModal = document.querySelector('#sheet-modal');
sheetModal.breakpoints = [0.1, 0.5, 1];
sheetModal.initialBreakpoint = 0.5;
cardModal.swipeToClose = true;
cardModal.presentingElement = document.querySelector('ion-app');
</script>
Using JavaScript
customElements.define('modal-page', class extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-title>Modal Header</ion-title>
<ion-buttons slot="primary">
<ion-button onClick="dismissModal()">
<ion-icon slot="icon-only" name="close"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
Modal Content
</ion-content>`;
}
});
function presentModal() {
// create the modal with the `modal-page` component
const modalElement = document.createElement('ion-modal');
modalElement.component = 'modal-page';
modalElement.cssClass = 'my-custom-class';
// present the modal
document.body.appendChild(modalElement);
return modalElement.present();
}
If you need a wrapper element inside of your modal component, we recommend using a
<div class="ion-page">
so that the component dimensions are still computed properly.
Passing Data
During creation of a modal, data can be passed in through the componentProps
. The previous example can be written to include data:
const modalElement = document.createElement('ion-modal');
modalElement.component = 'modal-page';
modalElement.cssClass = 'my-custom-class';
modalElement.componentProps = {
'firstName': 'Douglas',
'lastName': 'Adams',
'middleInitial': 'N'
};
To get the data passed into the componentProps
, query for the modal in the modal-page
:
customElements.define('modal-page', class extends HTMLElement {
connectedCallback() {
const modalElement = document.querySelector('ion-modal');
console.log(modalElement.componentProps.firstName);
...
}
}
Dismissing a Modal
A modal can be dismissed by calling the dismiss method and optionally passing any data from the modal.
async function dismissModal() {
await modal.dismiss({
'dismissed': true
});
}
After being dismissed, the data can be read in through the onWillDismiss
or onDidDismiss
attached to the modal after creation:
const { data } = await modalElement.onWillDismiss();
console.log(data);
Card Modals
Modals in iOS mode have the ability to be presented in a card-style and swiped to close. The card-style presentation and swipe to close gesture are not mutually exclusive, meaning you can pick and choose which features you want to use. For example, you can have a card-style modal that cannot be swiped or a full sized modal that can be swiped.
Card style modals when running on iPhone-sized devices do not have backdrops. As a result, the
--backdrop-opacity
variable will not have any effect.
const modalElement = document.createElement('ion-modal');
modalElement.component = 'modal-page';
modalElement.cssClass = 'my-custom-class';
modalElement.swipeToClose = true;
modalElement.presentingElement = document.querySelector('ion-nav');
In most scenarios, using the ion-nav
element as the presentingElement
is fine. In cases where you are presenting a card-style modal from within a modal, you should pass in the top-most ion-modal
element as the presentingElement
.
const modalElement = document.createElement('ion-modal');
modalElement.component = 'modal-page';
modalElement.cssClass = 'my-custom-class';
modalElement.swipeToClose = true;
modalElement.presentingElement = await modalController.getTop(); // Get the top-most ion-modal
Sheet Modals
const modalElement = document.createElement('ion-modal');
modalElement.component = 'modal-page';
modalElement.initialBreakpoint = 0.5;
modalElement.breakpoints = [0, 0.5, 1];
Inline Modal
import { React, useRef } from 'react';
const App: React.FC = () => {
const routerRef = useRef<HTMLIonRouterOutletElement | null>(null);
return (
<IonApp>
<IonReactRouter>
<IonRouterOutlet ref={routerRef}>
<Route path="/home" render={() => <Home router={routerRef.current} />} exact={true} />
</IonRouterOutlet>
</IonReactRouter>
</IonApp>
)
};
...
interface Props {
router: HTMLIonRouterOutletElement | null;
}
import React from 'react';
import AppReactComponent from './AppReactComponent';
import { IonModal, IonContent, IonButton } from '@ionic/react';
export const ModalExample: React.FC<Props> = ({ router }) => {
return (
<>
{/* Default */}
<IonModal isOpen={true}>
<IonContent>Modal Content</IonContent>
</IonModal>
{/* Use a trigger */}
<IonButton id="trigger-button">Click to open modal</IonButton>
<IonModal trigger="trigger-button">
<IonContent>Modal Content</IonContent>
</IonModal>
{/* Sheet Modal */}
<IonModal
isOpen={true}
breakpoints={[0.1, 0.5, 1]}
initialBreakpoint={0.5}
>
<IonContent>Modal Content</IonContent>
</IonModal>
{/* Card Modal */}
<IonModal
isOpen={true}
swipeToClose={true}
presentingElement={router || undefined}
>
<IonContent>Modal Content</IonContent>
</IonModal>
{/* Passing Props */}
<IonModal isOpen={true}>
<AppReactComponent title="Ionic"></AppReactComponent>
</IonModal>
</>
);
};
useIonModal Hook
useIonModal
requires being a descendant of<IonApp>
. If you need to use a modal outside of an<IonApp>
, consider using the component method instead.
/* Using with useIonModal Hook */
import React, { useState } from 'react';
import { IonButton, IonContent, IonPage, useIonModal } from '@ionic/react';
const Body: React.FC<{
count: number;
onDismiss: () => void;
onIncrement: () => void;
}> = ({ count, onDismiss, onIncrement }) => (
<div>
count: {count}
<IonButton expand="block" onClick={() => onIncrement()}>
Increment Count
</IonButton>
<IonButton expand="block" onClick={() => onDismiss()}>
Close
</IonButton>
</div>
);
const ModalExample: React.FC = () => {
const [count, setCount] = useState(0);
const handleIncrement = () => {
setCount(count + 1);
};
const handleDismiss = () => {
dismiss();
};
/**
* First parameter is the component to show, second is the props to pass
*/
const [present, dismiss] = useIonModal(Body, {
count,
onDismiss: handleDismiss,
onIncrement: handleIncrement,
});
return (
<IonPage>
<IonContent fullscreen>
<IonButton
expand="block"
onClick={() => {
present({
cssClass: 'my-class',
});
}}
>
Show Modal
</IonButton>
<div>Count: {count}</div>
</IonContent>
</IonPage>
);
};
/* Using with IonModal Component */
import React, { useState } from 'react';
import { IonModal, IonButton, IonContent } from '@ionic/react';
export const ModalExample: React.FC = () => {
const [showModal, setShowModal] = useState(false);
return (
<IonContent>
<IonModal isOpen={showModal} cssClass='my-custom-class'>
<p>This is modal content</p>
<IonButton onClick={() => setShowModal(false)}>Close Modal</IonButton>
</IonModal>
<IonButton onClick={() => setShowModal(true)}>Show Modal</IonButton>
</IonContent>
);
};
Card Modals
Modals in iOS mode have the ability to be presented in a card-style and swiped to close. The card-style presentation and swipe to close gesture are not mutually exclusive, meaning you can pick and choose which features you want to use. For example, you can have a card-style modal that cannot be swiped or a full sized modal that can be swiped.
Card style modals when running on iPhone-sized devices do not have backdrops. As a result, the
--backdrop-opacity
variable will not have any effect.
const App: React.FC = () => {
const routerRef = useRef<HTMLIonRouterOutletElement | null>(null);
return (
<IonApp>
<IonReactRouter>
<IonRouterOutlet ref={routerRef}>
<Route path="/home" render={() => <Home router={routerRef.current} />} exact={true} />
</IonRouterOutlet>
</IonReactRouter>
</IonApp>
)
};
...
interface HomePageProps {
router: HTMLIonRouterOutletElement | null;
}
const Home: React.FC<HomePageProps> = ({ router }) => {
const [showModal, setShowModal] = useState(false);
return (
<IonPage>
<IonContent>
<IonModal
isOpen={showModal}
cssClass='my-custom-class'
swipeToClose={true}
presentingElement={router || undefined}
onDidDismiss={() => setShowModal(false)}>
<p>This is modal content</p>
</IonModal>
</IonContent>
</IonPage>
);
};
In most scenarios, setting a ref on IonRouterOutlet
and passing that ref's current
value to presentingElement
is fine. In cases where you are presenting a card-style modal from within another modal, you should pass in the top-most IonModal
ref as the presentingElement
.
<IonModal
ref={firstModalRef}
isOpen={showModal}
cssClass='my-custom-class'
swipeToClose={true}
presentingElement={router || undefined}
onDidDismiss={() => setShowModal(false)}>
<p>This is modal content</p>
<IonButton onClick={() => setShow2ndModal(true)}>Show 2nd Modal</IonButton>
<IonButton onClick={() => setShowModal(false)}>Close Modal</IonButton>
</IonModal>
<IonModal
isOpen={show2ndModal}
cssClass='my-custom-class'
presentingElement={firstModalRef.current}
swipeToClose={true}
onDidDismiss={() => setShow2ndModal(false)}>
<p>This is more modal content</p>
<IonButton onClick={() => setShow2ndModal(false)}>Close Modal</IonButton>
</IonModal>
Sheet Modals
const App: React.FC = () => {
const routerRef = useRef<HTMLIonRouterOutletElement | null>(null);
return (
<IonApp>
<IonReactRouter>
<IonRouterOutlet ref={routerRef}>
<Route path="/home" render={() => <Home router={routerRef.current} />} exact={true} />
</IonRouterOutlet>
</IonReactRouter>
</IonApp>
)
};
...
const Home: React.FC = () => {
const [showModal, setShowModal] = useState(false);
return (
<IonPage>
<IonContent>
<IonModal
isOpen={showModal}
initialBreakpoint={0.5}
breakpoints={[0, 0.5, 1]}
onDidDismiss={() => setShowModal(false)}>
<p>This is modal content</p>
</IonModal>
</IonContent>
</IonPage>
);
};
Inline Modal
import { Component, Element, h } from '@stencil/core';
@Component({
tag: 'modal-example',
styleUrl: 'modal-example.css'
})
export class ModalExample {
@Element() el: any;
componentDidLoad() {
this.routerOutlet = this.el.closest('ion-router-outlet');
}
render() {
return (
<div>
{/* Default */}
<ion-modal isOpen={true}>
<ion-content>Modal Content</ion-content>
</ion-modal>
{/* Use a trigger */}
<ion-button id="trigger-button">Click to open modal</ion-button>
<ion-modal trigger="trigger-button">
<ion-content>Modal Content</ion-content>
</ion-modal>
{/* Sheet Modal */}
<ion-modal
isOpen={true}
breakpoints={[0.1, 0.5, 1]}
initialBreakpoint={0.5}
>
<ion-content>Modal Content</ion-content>
</ion-modal>
{/* Card Modal */}
<ion-modal
isOpen={true}
swipeToClose={true}
presentingElement={this.routerOutlet}
>
<ion-content>Modal Content</ion-content>
</ion-modal>
{/* Passing Props */}
<ion-modal isOpen={true}>
<app-stencil-component title="Ionic"></app-stencil-component>
</ion-modal>
</div>
)
}
}
Modal Controller
import { Component, h } from '@stencil/core';
import { modalController } from '@ionic/core';
@Component({
tag: 'modal-example',
styleUrl: 'modal-example.css'
})
export class ModalExample {
async presentModal() {
const modal = await modalController.create({
component: 'page-modal',
cssClass: 'my-custom-class'
});
await modal.present();
}
}
import { Component, h } from '@stencil/core';
@Component({
tag: 'page-modal',
styleUrl: 'page-modal.css',
})
export class PageModal {
render() {
return [
<ion-list>
<ion-item>
<ion-label>Documentation</ion-label>
</ion-item>
<ion-item>
<ion-label>Feedback</ion-label>
</ion-item>
<ion-item>
<ion-label>Settings</ion-label>
</ion-item>
</ion-list>
];
}
}
If you need a wrapper element inside of your modal component, we recommend using a
<div class="ion-page">
so that the component dimensions are still computed properly.
Passing Data
During creation of a modal, data can be passed in through the componentProps
.
The previous example can be written to include data:
async presentModal() {
const modal = await modalController.create({
component: 'page-modal',
cssClass: 'my-custom-class',
componentProps: {
'firstName': 'Douglas',
'lastName': 'Adams',
'middleInitial': 'N'
}
});
await modal.present();
}
To get the data passed into the componentProps
, set each one as a @Prop
:
import { Component, Prop, h } from '@stencil/core';
@Component({
tag: 'page-modal',
styleUrl: 'page-modal.css',
})
export class PageModal {
// Data passed in by componentProps
@Prop() firstName: string;
@Prop() lastName: string;
@Prop() middleInitial: string;
}
Dismissing a Modal
A modal can be dismissed by calling the dismiss method on the modal controller and optionally passing any data from the modal.
export class ModalPage {
...
dismiss(data?: any) {
// dismiss the closest modal and optionally pass back data
(this.el.closest('ion-modal') as any).dismiss({
'dismissed': true
});
}
}
After being dismissed, the data can be read in through the onWillDismiss
or onDidDismiss
attached to the modal after creation:
const { data } = await modal.onWillDismiss();
console.log(data);
Card Modals
Modals in iOS mode have the ability to be presented in a card-style and swiped to close. The card-style presentation and swipe to close gesture are not mutually exclusive, meaning you can pick and choose which features you want to use. For example, you can have a card-style modal that cannot be swiped or a full sized modal that can be swiped.
Card style modals when running on iPhone-sized devices do not have backdrops. As a result, the
--backdrop-opacity
variable will not have any effect.
import { Component, Element, h } from '@stencil/core';
import { modalController } from '@ionic/core';
@Component({
tag: 'modal-example',
styleUrl: 'modal-example.css'
})
export class ModalExample {
@Element() el: any;
async presentModal() {
const modal = await modalController.create({
component: 'page-modal',
cssClass: 'my-custom-class',
swipeToClose: true,
presentingElement: this.el.closest('ion-router-outlet'),
});
await modal.present();
}
}
In most scenarios, using the ion-router-outlet
element as the presentingElement
is fine. In cases where you are presenting a card-style modal from within another modal, you should pass in the top-most ion-modal
element as the presentingElement
.
async presentModal() {
const modal = await modalController.create({
component: 'page-modal',
cssClass: 'my-custom-class',
swipeToClose: true,
presentingElement: await modalController.getTop() // Get the top-most ion-modal
});
await modal.present();
}
Sheet Modals
Controller
import { Component, Element, h } from '@stencil/core';
import { modalController } from '@ionic/core';
@Component({
tag: 'modal-example',
styleUrl: 'modal-example.css'
})
export class ModalExample {
@Element() el: any;
async presentModal() {
const modal = await modalController.create({
component: 'page-modal',
initialBreakpoint: 0.5,
breakpoints: [0, 0.5, 1]
});
await modal.present();
}
}
Inline
import { Component, State, h } from '@stencil/core';
@Component({
tag: 'modal-example',
styleUrl: 'modal-example.css'
})
export class ModalExample {
@State() isModalOpen: boolean = false;
render() {
return [
<ion-modal
isOpen={isModalOpen}
initialBreakpoint={0.5}
breakpoints={[0, 0.5, 1]}
>
<page-modal></page-modal>
<ion-modal>
]
}
}
Inline Modal
<!-- Default -->
<ion-modal :is-open="true">
<ion-content>Modal Content</ion-content>
</ion-modal>
<!-- Use a trigger -->
<ion-button id="trigger-button">Click to open modal</ion-button>
<ion-modal trigger="trigger-button">
<ion-content>Modal Content</ion-content>
</ion-modal>
<!-- Sheet Modal -->
<ion-modal
:is-open="true"
:breakpoints="[0.1, 0.5, 1]"
:initialBreakpoint="0.5"
>
<ion-content>Modal Content</ion-content>
</ion-modal>
<!-- Card Modal -->
<ion-modal
:is-open="true"
:swipe-to-close="true"
:presenting-element="$parent.$refs.ionRouterOutlet"
>
<ion-content>Modal Content</ion-content>
</ion-modal>
<!-- Passing Props -->
<ion-modal :is-open="true">
<app-vue-component title="Ionic"></app-vue-component>
</ion-modal>
<script>
import { IonModal, IonButton, IonContent } from '@ionic/vue';
import { defineComponent } from 'vue';
import AppVueComponent from './AppVueComponent.vue'
export default defineComponent({
components: { IonModal, IonButton, IonContent, AppVueComponent }
});
</script>
Modal Controller
<template>
<ion-header>
<ion-toolbar>
<ion-title>{{ title }}</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
{{ content }}
</ion-content>
</template>
<script>
import { IonContent, IonHeader, IonTitle, IonToolbar } from '@ionic/vue';
import { defineComponent } from 'vue';
export default defineComponent({
name: 'Modal',
props: {
title: { type: String, default: 'Super Modal' },
},
data() {
return {
content: 'Content',
}
},
components: { IonContent, IonHeader, IonTitle, IonToolbar }
});
</script>
<template>
<ion-page>
<ion-content class="ion-padding">
<ion-button @click="openModal">Open Modal</ion-button>
</ion-content>
</ion-page>
</template>
<script>
import { IonButton, IonContent, IonPage, modalController } from '@ionic/vue';
import Modal from './modal.vue'
export default {
components: { IonButton, IonContent, IonPage },
methods: {
async openModal() {
const modal = await modalController
.create({
component: Modal,
cssClass: 'my-custom-class',
componentProps: {
title: 'New Title'
},
})
return modal.present();
},
},
}
</script>
Developers can also use this component directly in their template:
<template>
<ion-button @click="setOpen(true)">Show Modal</ion-button>
<ion-modal
:is-open="isOpenRef"
css-class="my-custom-class"
@didDismiss="setOpen(false)"
>
<Modal :data="data"></Modal>
</ion-modal>
</template>
<script>
import { IonModal, IonButton } from '@ionic/vue';
import { defineComponent, ref } from 'vue';
import Modal from './modal.vue'
export default defineComponent({
components: { IonModal, IonButton, Modal },
setup() {
const isOpenRef = ref(false);
const setOpen = (state: boolean) => isOpenRef.value = state;
const data = { content: 'New Content' };
return { isOpenRef, setOpen, data }
}
});
</script>
If you need a wrapper element inside of your modal component, we recommend using an
<ion-page>
so that the component dimensions are still computed properly.
Card Modals
Modals in iOS mode have the ability to be presented in a card-style and swiped to close. The card-style presentation and swipe to close gesture are not mutually exclusive, meaning you can pick and choose which features you want to use. For example, you can have a card-style modal that cannot be swiped or a full sized modal that can be swiped.
Card style modals when running on iPhone-sized devices do not have backdrops. As a result, the
--backdrop-opacity
variable will not have any effect.
<template>
<ion-page>
<ion-content>
<ion-button @click="setOpen(true)">Show Modal</ion-button>
<ion-modal
:is-open="isOpenRef"
css-class="my-custom-class"
:swipe-to-close="true"
:presenting-element="$parent.$refs.ionRouterOutlet"
@didDismiss="setOpen(false)"
>
<Modal :data="data"></Modal>
</ion-modal>
</ion-content>
</ion-page>
</template>
<script lang="ts">
import { IonModal, IonButton, IonContent, IonPage } from '@ionic/vue';
import { defineComponent, ref } from 'vue';
import Modal from './modal.vue'
export default defineComponent({
components: { IonModal, IonButton, Modal, IonContent, IonPage },
setup() {
const isOpenRef = ref(false);
const setOpen = (state: boolean) => isOpenRef.value = state;
const data = { content: 'New Content' };
return { isOpenRef, setOpen, data }
}
});
</script>
Sheet Modals
<template>
<ion-page>
<ion-content class="ion-padding">
<ion-button @click="openModal()">Open Modal</ion-button>
</ion-content>
</ion-page>
</template>
<script>
import { IonButton, IonContent, IonPage, modalController } from '@ionic/vue';
import Modal from './modal.vue'
export default {
components: { IonButton, IonContent, IonPage },
methods: {
async openModal() {
const modal = await modalController
.create({
component: Modal,
initialBreakpoint: 0.5,
breakpoints: [0, 0.5, 1]
})
return modal.present();
},
},
}
</script>
Properties
animated
Description | If true , the modal will animate. |
Attribute | animated |
Type | boolean |
Default | true |
backdropBreakpoint
Description | A decimal value between 0 and 1 that indicates the point after which the backdrop will begin to fade in when using a sheet modal. Prior to this point, the backdrop will be hidden and the content underneath the sheet can be interacted with. This value is exclusive meaning the backdrop will become active after the value specified. |
Attribute | backdrop-breakpoint |
Type | number |
Default | 0 |
backdropDismiss
Description | If true , the modal will be dismissed when the backdrop is clicked. |
Attribute | backdrop-dismiss |
Type | boolean |
Default | true |
breakpoints
Description | The breakpoints to use when creating a sheet modal. Each value in the array must be a decimal between 0 and 1 where 0 indicates the modal is fully closed and 1 indicates the modal is fully open. Values are relative to the height of the modal, not the height of the screen. One of the values in this array must be the value of the initialBreakpoint property.For example: [0, .25, .5, 1] |
Attribute | undefined |
Type | number[] ๏ฝ undefined |
Default | undefined |
enterAnimation
Description | Animation to use when the modal is presented. |
Attribute | undefined |
Type | ((baseEl: any, opts?: any) => Animation) ๏ฝ undefined |
Default | undefined |
handle
Description | The horizontal line that displays at the top of a sheet modal. It is true by default whensetting the breakpoints and initialBreakpoint properties. |
Attribute | handle |
Type | boolean ๏ฝ undefined |
Default | undefined |
htmlAttributes
Description | Additional attributes to pass to the modal. |
Attribute | undefined |
Type | ModalAttributes ๏ฝ undefined |
Default | undefined |
initialBreakpoint
Description | A decimal value between 0 and 1 that indicates the initial point the modal will open at when creating a sheet modal. This value must also be listed in the breakpoints array. |
Attribute | initial-breakpoint |
Type | number ๏ฝ undefined |
Default | undefined |
isOpen
Description | If true , the modal will open. If false , the modal will close.Use this if you need finer grained control over presentation, otherwise just use the modalController or the trigger property.Note: isOpen will not automatically be set back to false whenthe modal dismisses. You will need to do that in your code. |
Attribute | is-open |
Type | boolean |
Default | false |
keyboardClose
Description | If true , the keyboard will be automatically dismissed when the overlay is presented. |
Attribute | keyboard-close |
Type | boolean |
Default | true |
leaveAnimation
Description | Animation to use when the modal is dismissed. |
Attribute | undefined |
Type | ((baseEl: any, opts?: any) => Animation) ๏ฝ undefined |
Default | undefined |
mode
Description | The mode determines which platform styles to use. |
Attribute | mode |
Type | "ios" ๏ฝ "md" |
Default | undefined |
presentingElement
Description | The element that presented the modal. This is used for card presentation effects and for stacking multiple modals on top of each other. Only applies in iOS mode. |
Attribute | undefined |
Type | HTMLElement ๏ฝ undefined |
Default | undefined |
showBackdrop
Description | If true , a backdrop will be displayed behind the modal. |
Attribute | show-backdrop |
Type | boolean |
Default | true |
swipeToClose
Description | If true , the modal can be swiped to dismiss. Only applies in iOS mode. |
Attribute | swipe-to-close |
Type | boolean |
Default | false |
trigger
Description | An ID corresponding to the trigger element that causes the modal to open when clicked. |
Attribute | trigger |
Type | string ๏ฝ undefined |
Default | undefined |
Events
Name | Description |
---|---|
didDismiss | Emitted after the modal has dismissed. |
Shorthand for ionModalDidDismiss. | |
didPresent | Emitted after the modal has presented. |
Shorthand for ionModalWillDismiss. | |
ionModalDidDismiss | Emitted after the modal has dismissed. |
ionModalDidPresent | Emitted after the modal has presented. |
ionModalWillDismiss | Emitted before the modal has dismissed. |
ionModalWillPresent | Emitted before the modal has presented. |
willDismiss | Emitted before the modal has dismissed. |
Shorthand for ionModalWillDismiss. | |
willPresent | Emitted before the modal has presented. |
Shorthand for ionModalWillPresent. |
Methods
dismiss
Description | Dismiss the modal overlay after it has been presented. |
Signature | dismiss(data?: any, role?: string ๏ฝ undefined) => Promise<boolean> |
onDidDismiss
Description | Returns a promise that resolves when the modal did dismiss. |
Signature | onDidDismiss<T = any>() => Promise<OverlayEventDetail<T>> |
onWillDismiss
Description | Returns a promise that resolves when the modal will dismiss. |
Signature | onWillDismiss<T = any>() => Promise<OverlayEventDetail<T>> |
present
Description | Present the modal overlay after it has been created. |
Signature | present() => Promise<void> |
CSS Shadow Parts
Name | Description |
---|---|
backdrop | The ion-backdrop element. |
content | The wrapper element for the default slot. |
handle | The handle that is displayed at the top of the sheet modal when handle="true" . |
CSS Custom Properties
Name | Description |
---|---|
--backdrop-opacity | Opacity of the backdrop |
--background | Background of the modal content |
--border-color | Border color of the modal content |
--border-radius | Border radius of the modal content |
--border-style | Border style of the modal content |
--border-width | Border width of the modal content |
--height | Height of the modal |
--max-height | Maximum height of the modal |
--max-width | Maximum width of the modal |
--min-height | Minimum height of the modal |
--min-width | Minimum width of the modal |
--width | Width of the modal |
Slots
Name | Description |
---|---|
= Content is placed inside of the .modal-content element. |