Skip to main content
Version: v6

ion-modal

shadow

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.

Using isOpen

When using inline modals, developers have access to the isOpen property which allows them to control the state of the modal through the state of their application. This is helpful for opening a modal after a state change without needing to explicitly call the present method on ion-modal.

Note that isOpen uses a one way data binding. This means that a reactive variable that sets isOpen to true will not be automatically set to false when the modal dismisses. Developers need to listen for the ionModalDidDismiss or didDismiss events and set the reactive variable to false themselves.

The reason for this is it prevents the internals of ion-modal from being tightly coupled with the state of the application. With a one way data binding, the modal only needs to concern itself with the boolean value that the reactive variable provides. With a two way data binding, the modal needs to concern itself with both the boolean value as well as the existence of the reactive variable itself. This can lead to non-deterministic behaviors and make applications harder to debug.

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 initialBreakpoint 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.

Preventing a Modal from Dismissing

When entering data into a modal, it is often desirable to have a way of preventing accidental data loss. The canDismiss property on ion-modal gives developers control over when a modal is allowed to dismiss.

There are two different ways of using the canDismiss property.

note

Note: When using a sheet modal, canDismiss will not be checked on swipe if there is no 0 breakpoint set. However, it will still be checked when pressing Esc or the hardware back button.

Setting a boolean value

Developers can set canDismiss to a boolean value. If canDismiss is true, then the modal will close when users attempt to dismiss the modal. If canDismiss is false, then the modal will not close when users attempt to dismiss the modal.

Setting a boolean value should be used when you need to require a particular action to be taken prior to a modal being dismissed. For example, if developers want to require that a "Terms of Use" checkbox is checked prior to closing the modal, they could set canDismiss to false initially and update it to true when the checkbox is checked.

Setting a callback function

Developers can set canDismiss to be a function. This function must return a Promise that resolves to either true or false. If the promise resolves to true, then the modal will dismiss. If the promise resolves to false, then the modal will not dismiss.

Setting a callback function should be used when you have complex dismissing criteria such as showing a confirmation dialog prior to dismissing the modal. The option that users select in this dialog can then be used to determine whether or not the modal should proceed with dismissing.

Note that setting a callback function will cause the swipe gesture to be interrupted when using a card or sheet modal. This is because Ionic does not know what your callback function will resolve to ahead of time.

Interfaces

ModalOptions

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;
}

ModalCustomEvent

While not required, this interface can be used in place of the CustomEvent interface for stronger typing with Ionic events emitted from this component.

interface ModalCustomEvent extends CustomEvent {
target: HTMLIonModalElement;
}

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?: { [key: string]: any };

enterAnimation?: AnimationBuilder;
leaveAnimation?: AnimationBuilder;

breakpoints?: number[];
initialBreakpoint?: number;
backdropBreakpoint?: number;
handle?: boolean;
}

Accessibility

Keyboard Navigation

KeyFunction
EscDismisses the modal

Screen Readers

Modals have the aria-modal attribute applied. This attribute can cause assistive technologies to limit navigation to the modal element's contents. As a result, using gestures that move to the next or previous items may not focus elements outside of the modal. This applies even when the backdrop is disabled in sheet modals using the backdropBreakpoint property.

Assistive technologies will not limit navigation to the modal element's contents if developers manually move focus. However, manually moving focus outside of a modal is not supported in Ionic for modals that have focus trapping enabled.

See https://w3c.github.io/aria/#aria-modal for more information.

Focus Trapping

When a modal is presented, focus will be trapped inside of the presented modal. Users can focus other interactive elements inside the modal but will never be able to focus interactive elements outside the modal while the modal is presented. For applications that present multiple stacked modals, focus will be trapped on the modal that was presented last.

Sheet modals that have had their backdrop disabled by the backdropBreakpoint property are not subject to focus trapping.

Sheet Modals

Sheet modals allow users to interact with content behind the modal when the backdropBreakpoint property is used. The backdrop will be disabled up to and including the specified backdropBreakpoint and will be enabled after it.

When the backdrop is disabled, users will be able to interact with elements outside the sheet modal using a pointer or keyboard. Assistive technologies may not focus outside the sheet modal by default due to the usage of aria-modal. We recommend avoiding features such as autofocus here as it can cause assistive technologies to jump between two interactive contexts without warning the user.

Usage

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>

<!-- Require Action Sheet confirmation before dismissing -->
<ion-modal [isOpen]="true" [canDismiss]="canDismiss">
<ng-template>
<ion-content>Modal Content</ion-content>
</ng-template>
</ion-modal>
import { Component } from '@angular/core';
import { IonRouterOutlet, ActionSheetController } from '@ionic/angular';

@Component({
selector: 'modal-example',
templateUrl: 'modal-example.html',
styleUrls: ['./modal-example.css']
})
export class ModalExample {
constructor(
public routerOutlet: IonRouterOutlet,
private actionSheetCtrl: ActionSheetController
) {}

async canDismiss() {
const actionSheet = await this.actionSheetCtrl.create({
header: 'Are you sure you want to discard your changes?',
buttons: [
{
text: 'Discard Changes',
role: 'destructive'
},
{
text: 'Keep Editing',
role: 'cancel'
}
]
});

await actionSheet.present();

const { role } = await actionSheet.onDidDismiss();

if (role === 'destructive') {
return true;
}

return false;
}
}
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 {
// The `ion-modal` element reference.
modal: HTMLElement;

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);

Accessing the Modal Element

When opening a modal with the modal controller, Ionic will assign the modal HTML element reference to the modal property on your component's class instance.

You can use this property to directly access the ion-modal element to add or remove classes or handle additional checks.

export class ModalPage implements OnInit {
// The `ion-modal` element reference.
modal: HTMLElement;

ngOnInit() {
console.log('The HTML ion-modal element', this.modal); // <ion-modal></ion-modal>
}
}

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.

Properties

animated

DescriptionIf true, the modal will animate.
Attributeanimated
Typeboolean
Defaulttrue

backdropBreakpoint

DescriptionA 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.
Attributebackdrop-breakpoint
Typenumber
Default0

backdropDismiss

DescriptionIf true, the modal will be dismissed when the backdrop is clicked.
Attributebackdrop-dismiss
Typeboolean
Defaulttrue

breakpoints

DescriptionThe 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]
Attributeundefined
Typenumber[] | undefined
Defaultundefined

enterAnimation

DescriptionAnimation to use when the modal is presented.
Attributeundefined
Type((baseEl: any, opts?: any) => Animation) | undefined
Defaultundefined

handle

DescriptionThe horizontal line that displays at the top of a sheet modal. It is true by default when setting the breakpoints and initialBreakpoint properties.
Attributehandle
Typeboolean | undefined
Defaultundefined

htmlAttributes

DescriptionAdditional attributes to pass to the modal.
Attributeundefined
TypeModalAttributes | undefined
Defaultundefined

initialBreakpoint

DescriptionA 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.
Attributeinitial-breakpoint
Typenumber | undefined
Defaultundefined

isOpen

DescriptionIf 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 when the modal dismisses. You will need to do that in your code.
Attributeis-open
Typeboolean
Defaultfalse

keyboardClose

DescriptionIf true, the keyboard will be automatically dismissed when the overlay is presented.
Attributekeyboard-close
Typeboolean
Defaulttrue

leaveAnimation

DescriptionAnimation to use when the modal is dismissed.
Attributeundefined
Type((baseEl: any, opts?: any) => Animation) | undefined
Defaultundefined

mode

DescriptionThe mode determines which platform styles to use.
Attributemode
Type"ios" | "md"
Defaultundefined

presentingElement

DescriptionThe 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.
Attributeundefined
TypeHTMLElement | undefined
Defaultundefined

showBackdrop

DescriptionIf true, a backdrop will be displayed behind the modal.
Attributeshow-backdrop
Typeboolean
Defaulttrue

swipeToClose

DescriptionIf true, the modal can be swiped to dismiss. Only applies in iOS mode.
Attributeswipe-to-close
Typeboolean
Defaultfalse

trigger

DescriptionAn ID corresponding to the trigger element that causes the modal to open when clicked.
Attributetrigger
Typestring | undefined
Defaultundefined

Events

NameDescription
didDismissEmitted after the modal has dismissed. Shorthand for ionModalDidDismiss.
didPresentEmitted after the modal has presented. Shorthand for ionModalWillDismiss.
ionModalDidDismissEmitted after the modal has dismissed.
ionModalDidPresentEmitted after the modal has presented.
ionModalWillDismissEmitted before the modal has dismissed.
ionModalWillPresentEmitted before the modal has presented.
willDismissEmitted before the modal has dismissed. Shorthand for ionModalWillDismiss.
willPresentEmitted before the modal has presented. Shorthand for ionModalWillPresent.

Methods

dismiss

DescriptionDismiss the modal overlay after it has been presented.
Signaturedismiss(data?: any, role?: string | undefined) => Promise<boolean>

onDidDismiss

DescriptionReturns a promise that resolves when the modal did dismiss.
SignatureonDidDismiss<T = any>() => Promise<OverlayEventDetail<T>>

onWillDismiss

DescriptionReturns a promise that resolves when the modal will dismiss.
SignatureonWillDismiss<T = any>() => Promise<OverlayEventDetail<T>>

present

DescriptionPresent the modal overlay after it has been created.
Signaturepresent() => Promise<void>

CSS Shadow Parts

NameDescription
backdropThe ion-backdrop element.
contentThe wrapper element for the default slot.
handleThe handle that is displayed at the top of the sheet modal when handle="true".

CSS Custom Properties

NameDescription
--backdrop-opacityOpacity of the backdrop
--backgroundBackground of the modal content
--border-colorBorder color of the modal content
--border-radiusBorder radius of the modal content
--border-styleBorder style of the modal content
--border-widthBorder width of the modal content
--heightHeight of the modal
--max-heightMaximum height of the modal
--max-widthMaximum width of the modal
--min-heightMinimum height of the modal
--min-widthMinimum width of the modal
--widthWidth of the modal

Slots

NameDescription
= Content is placed inside of the .modal-content element.
View Source