Skip to main content

Ionic Action Sheet: A Complete Guide for 2026

Learn to implement, style, and troubleshoot the Ionic Action Sheet in Angular, React, & Vue. A complete guide with code examples and advanced tips for 2026.

Martin Donadieu

Martin Donadieu

Content Marketer

Ionic Action Sheet: A Complete Guide for 2026

You’re probably in one of two situations right now. Either you need a clean way to show a few contextual actions without stuffing your screen with extra buttons, or you already shipped an ionic action sheet and discovered that the easy demo version isn’t the same thing as a production-ready implementation.

That gap matters. An action sheet looks simple, but it sits at the intersection of interaction design, framework APIs, platform behavior, accessibility, and post-release maintenance. If you only treat it as a popup with buttons, you’ll miss the parts that usually break late in QA.

Table of Contents

Introduction to the Ionic Action Sheet

The ionic action sheet is the right tool when the user needs to make a small, focused choice tied to the current context. Delete a draft. Replace a profile photo. Save, share, or archive a document. These actions matter, but they don’t deserve permanent space in the main layout.

In Ionic, the pattern has stayed consistent for a long time. Earlier Ionic apps used the $ionicActionSheet service, which TutorialsPoint describes as a pane that slides up from the bottom of the screen and is shown by injecting the service and calling show() in the controller. Modern apps use ion-action-sheet, but the interaction model is still recognizably the same, which makes the component one of the clearer examples of Ionic preserving mobile UI patterns across framework generations in the Ionic 1 action sheet documentation summary from TutorialsPoint.

That continuity is useful in real projects. It means the component isn’t a trendy abstraction that changed every release. It’s a stable mobile-first pattern that maps well to iOS and Android option menus, and it still feels natural in Angular, React, and Vue projects.

Why teams keep reaching for it

An action sheet works well when the user already understands the context and only needs a compact list of next steps. It works poorly when the user needs explanation, validation, or multiple form fields.

A simple rule helps:

  • Use an action sheet for short decision menus tied to a specific item.
  • Use an alert when you need confirmation with minimal options.
  • Use a modal when the user needs more content, inputs, or scrolling.

Practical rule: If the button labels can’t stand on their own without extra paragraph text, don’t force the interaction into an action sheet.

In hybrid apps, this pattern also fits neatly into the web-to-native model. The UI is simple enough to render in the web layer, while still feeling native on touch devices. If your team is building on Capacitor and wants a clearer mental model of that boundary, this breakdown of how Capacitor bridges web and native code is worth keeping in mind while you decide where the action sheet should live.

Understanding the Action Sheet Controller and API

The action sheet becomes easy to reason about once you stop thinking of it as just another inline component. It behaves more like a temporary overlay with a lifecycle. You create it, present it, wait for the user, and then handle the result after dismissal.

A flowchart diagram explaining the architecture, configuration, and API components of an Action Sheet controller.

Why the API is controller driven

In day-to-day Ionic work, the controller-based approach is usually the cleanest option because the action sheet is ephemeral. You don’t want a large chunk of template markup sitting in your page for a menu that only appears after a tap on an overflow icon.

The official Ionic docs define the Action Sheet as a modal dialog that requires user dismissal, and they place a lot of weight on dismissal lifecycle methods such as onDidDismiss for post-selection logic in the Ionic Action Sheet API docs. That design tells you how to structure your code. Present first. React after dismissal. Don’t wire critical logic to assumptions about timing.

The options that actually matter

Most teams only need a small subset of the API, but they need to use that subset correctly.

OptionWhat it doesWhy it matters
headerSets the top labelGood for context when actions could be ambiguous
subHeaderAdds secondary textUseful when actions need light clarification
buttonsDefines the available actionsThis is where behavior and visual emphasis live
cssClassAdds custom classesEssential for scoped styling instead of global hacks
modeForces iOS or MD stylingHelpful for controlled testing across platforms

Button configuration is where mistakes usually happen. A typical button can include:

  • text for the visible label.
  • icon if you want a visual cue.
  • handler for immediate callback logic.
  • role for semantic behavior and platform styling.

role is not decorative. Use destructive for dangerous actions like delete. Use cancel for the escape path. Those roles affect how the action sheet presents choices and how users read the list under pressure.

Dangerous actions belong at the edge of the choice set, not mixed into neutral actions with the same visual weight.

Dismissal is part of the contract

A common bug goes like this: a developer opens an action sheet, assumes the handler result is enough, then triggers navigation or state updates before the overlay has fully dismissed. That can produce janky transitions, stale state, or race conditions in tests.

Use the lifecycle intentionally:

  1. Create the sheet.
  2. await present().
  3. await onDidDismiss().
  4. Read the returned role or data.
  5. Trigger the next action.

That pattern is boring, and that’s why it works.

Here’s a plain Angular-style example of the shape:

const sheet = await this.actionSheetController.create({
  header: 'Photo options',
  buttons: [
    {
      text: 'Take Photo',
      icon: 'camera',
      handler: () => {
        console.log('take photo');
      }
    },
    {
      text: 'Delete Photo',
      role: 'destructive',
      icon: 'trash'
    },
    {
      text: 'Cancel',
      role: 'cancel'
    }
  ]
});

await sheet.present();

const result = await sheet.onDidDismiss();
console.log('dismissed with role:', result.role);

If you remember only one thing from the API, remember this: an ionic action sheet is not finished when it appears. It’s finished when it dismisses.

Implementation Examples for Angular React and Vue

The syntax changes across frameworks, but the mental model doesn’t. Every version creates the same interaction: the user taps the avatar, sees options for the profile photo, chooses one action, and the app responds after the overlay closes.

Three mobile app UI screen designs labeled Angular, React, and Vue displaying a food delivery interface.

If you’re also handling offline states for media uploads, this guide to creating an offline screen in Vue Angular React pairs well with the examples below because photo actions often lead straight into network-dependent flows.

Angular example

In Ionic Angular, the most common approach is injecting ActionSheetController into the component or page.

import { Component } from '@angular/core';
import { ActionSheetController } from '@ionic/angular';

@Component({
  selector: 'app-profile-photo',
  template: `
    <ion-button expand="block" (click)="openPhotoActions()">
      Profile Photo Options
    </ion-button>
  `
})
export class ProfilePhotoComponent {
  constructor(private actionSheetController: ActionSheetController) {}

  async openPhotoActions() {
    const actionSheet = await this.actionSheetController.create({
      header: 'Profile photo',
      subHeader: 'Choose what to do next',
      buttons: [
        {
          text: 'Take Photo',
          icon: 'camera',
          handler: () => {
            console.log('Open camera flow');
          }
        },
        {
          text: 'Choose from Library',
          icon: 'images',
          handler: () => {
            console.log('Open photo library flow');
          }
        },
        {
          text: 'Remove Current Photo',
          role: 'destructive',
          icon: 'trash',
          handler: () => {
            console.log('Remove current photo');
          }
        },
        {
          text: 'Cancel',
          role: 'cancel'
        }
      ]
    });

    await actionSheet.present();

    const { role } = await actionSheet.onDidDismiss();
    console.log('Action sheet dismissed with role:', role);
  }
}

Angular teams usually go wrong in one of two places. They either move too much logic into the button handlers, or they forget that the dismissal promise is the safer place to coordinate UI transitions.

React example

In Ionic React, useIonActionSheet gives you a compact functional API that fits naturally with event handlers.

import React from 'react';
import { IonButton, useIonActionSheet } from '@ionic/react';

const ProfilePhotoActions: React.FC = () => {
  const [presentActionSheet] = useIonActionSheet();

  const openPhotoActions = () => {
    presentActionSheet({
      header: 'Profile photo',
      subHeader: 'Choose what to do next',
      buttons: [
        {
          text: 'Take Photo',
          icon: 'camera',
          handler: () => {
            console.log('Open camera flow');
          }
        },
        {
          text: 'Choose from Library',
          icon: 'images',
          handler: () => {
            console.log('Open photo library flow');
          }
        },
        {
          text: 'Remove Current Photo',
          role: 'destructive',
          icon: 'trash',
          handler: () => {
            console.log('Remove current photo');
          }
        },
        {
          text: 'Cancel',
          role: 'cancel'
        }
      ],
      onDidDismiss: (event) => {
        console.log('Dismissed with role:', event.detail.role);
      }
    });
  };

  return (
    <IonButton expand="block" onClick={openPhotoActions}>
      Profile Photo Options
    </IonButton>
  );
};

export default ProfilePhotoActions;

React’s hook API is ergonomic, but the same rule applies. Keep the immediate handler focused on the chosen action. Use dismissal callbacks for cleanup, analytics, or follow-up UI state.

Vue example

In Ionic Vue, actionSheetController works cleanly inside the Composition API.

<template>
  <ion-button expand="block" @click="openPhotoActions">
    Profile Photo Options
  </ion-button>
</template>

<script setup lang="ts">
import { IonButton, actionSheetController } from '@ionic/vue';

const openPhotoActions = async () => {
  const actionSheet = await actionSheetController.create({
    header: 'Profile photo',
    subHeader: 'Choose what to do next',
    buttons: [
      {
        text: 'Take Photo',
        icon: 'camera',
        handler: () => {
          console.log('Open camera flow');
        }
      },
      {
        text: 'Choose from Library',
        icon: 'images',
        handler: () => {
          console.log('Open photo library flow');
        }
      },
      {
        text: 'Remove Current Photo',
        role: 'destructive',
        icon: 'trash',
        handler: () => {
          console.log('Remove current photo');
        }
      },
      {
        text: 'Cancel',
        role: 'cancel'
      }
    ]
  });

  await actionSheet.present();

  const result = await actionSheet.onDidDismiss();
  console.log('Dismissed with role:', result.role);
};
</script>

A practical difference in Vue projects is where you keep side effects. If your app uses composables for camera or file-picker logic, call those from the handlers and leave the controller code thin.

Keep your framework-specific code small. The business logic for camera, upload, delete, and analytics should live outside the action sheet setup.

Customization and Styling with CSS

The default ionic action sheet styling is usually good enough for a prototype. It isn’t always good enough for a branded app, and it definitely isn’t enough when design wants tighter spacing, different typography, or a more obvious destructive action.

A web design presentation slide demonstrating six different CSS customization and styling techniques featuring apple-themed graphic design examples.

If your team is trying to make the whole app feel less like a generic web wrapper and more like a native product, this article on basic JS and CSS config for a native app look is a useful companion to action sheet styling.

Start with cssClass before global overrides

The first styling rule is simple. Don’t target every action sheet in the app unless you mean to. Use cssClass to scope a particular variant.

const sheet = await actionSheetController.create({
  header: 'File actions',
  cssClass: 'file-actions-sheet',
  buttons: [
    { text: 'Rename' },
    { text: 'Delete', role: 'destructive' },
    { text: 'Cancel', role: 'cancel' }
  ]
});

Then style only that instance:

.file-actions-sheet {
  --background: #101418;
  --color: #f5f7fa;
  --backdrop-opacity: 0.4;
}

That approach scales better than chasing selectors later.

Use custom properties for broad theming

CSS custom properties are the fastest way to change the overall feel without fighting the component structure.

Common use cases include:

  • Background and text color when your app has a dark custom palette.
  • Backdrop opacity when the default dimming feels too weak or too heavy.
  • Spacing and sizing when the visual density should match the rest of your interface.
.file-actions-sheet {
  --background: #1b1f24;
  --color: #ffffff;
  --backdrop-opacity: 0.32;
  --button-color: #dce3ea;
  --button-background-hover: #2a3138;
}

Use shadow parts when you need precision

Once design asks for targeted changes, custom properties may not be enough. That’s where Shadow Parts matter. They let you style internal areas of the action sheet more directly.

.file-actions-sheet::part(container) {
  border-radius: 18px 18px 0 0;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.24);
}

.file-actions-sheet::part(button) {
  font-weight: 600;
  letter-spacing: 0.01em;
}

.file-actions-sheet::part(backdrop) {
  backdrop-filter: blur(4px);
}

What usually doesn’t work well is over-styling the component until it stops feeling like a system-level choice menu. If you need rich cards, thumbnails, long descriptions, or complex row layouts, you’ve outgrown the action sheet pattern.

A good customization pass should make the component fit your app, not disguise what it is.

Advanced Topics and Platform Considerations

Production action sheets live in a bigger decision space than most tutorials admit. You’re not only choosing button labels. You’re deciding whether the overlay should be rendered by Ionic’s web layer or delegated to native UI, how strongly you want platform-specific behavior, and how to make sure the sheet remains understandable to all users.

A split image showing abstract 3D shapes on black and a fig on stone on green background.

Web component or native plugin

If you’re building a standard Ionic app, ion-action-sheet is usually the default. It’s flexible, easy to style, and works consistently with the rest of your app’s overlay system.

If your app is Capacitor-based and you want the host operating system to render the sheet, the native route is @capacitor/action-sheet. Ionic documents that plugin around showActions(options) -> Promise<ShowActionsResult>, installed with npm install @capacitor/action-sheet and synced with npx cap sync, while also noting that PWA Elements are required in web and PWA contexts in the Capacitor Action Sheet plugin docs.

That gives you a practical trade-off table:

ChoiceStrengthCost
ion-action-sheetEasier theming and shared web UI patternsSlightly less native fidelity
@capacitor/action-sheetHost OS rendering and stronger platform feelMore implementation constraints across browser and PWA contexts

Use the web component when visual consistency with your app matters more. Use the native plugin when platform fidelity matters more than deep CSS control.

Platform mode and accessibility details

Ionic can adapt to iOS and Material Design modes, and that affects spacing, motion, and overall visual tone. Don’t assume your styling behaves the same way in both modes. Test both intentionally, especially if your team forces a single mode across all platforms.

Accessibility also gets overlooked because action sheets feel small. The basics still matter:

  • Use clear button text that makes sense out of context.
  • Reserve destructive for risky actions so the interface communicates intent.
  • Keep cancel explicit so the user has a clear exit path.
  • Avoid decorative ambiguity where multiple actions sound similar but have very different outcomes.

A user with a screen reader or cognitive load constraints doesn’t experience “simple” overlays as simple if the labels are vague.

The sharp edge here is that the native and web approaches solve different problems. The web component gives you more control over appearance and integration. The native plugin gives you stronger platform alignment. Neither is automatically better. The right answer depends on whether your current app pain is visual consistency, implementation speed, or system-native behavior.

Troubleshooting Pitfalls and Shipping Live UI Fixes

Most ionic action sheet bugs don’t appear when you first wire up three buttons and tap through them in a simulator. They show up later, when the sheet is styled, tested on newer devices, and combined with real navigation and state transitions.

The bugs that show up after the demo works

The first class of bug is timing. Logic runs too early because the code doesn’t wait for dismissal. You see route changes while the overlay is still animating, or state updates that race against another component’s render.

The second class is layout. A known Ionic issue reports that the action sheet can overlap the bottom safe area on some iOS device conditions, especially when --ion-safe-area-bottom is nonzero, and the issue report notes it can even be reproduced in Ionic’s own docs demo in the GitHub issue about bottom safe area overlap. This is exactly the kind of problem teams miss until late QA because it depends on device shape, mode, and custom CSS.

A practical safe area fix

If your app shows the sheet too close to the home indicator area, start with a scoped override rather than a broad global patch.

.safe-area-sheet::part(container) {
  padding-bottom: calc(env(safe-area-inset-bottom) + 8px);
}

Then apply the class when creating the action sheet:

const sheet = await actionSheetController.create({
  header: 'More actions',
  cssClass: 'safe-area-sheet',
  buttons: [
    { text: 'Archive' },
    { text: 'Delete', role: 'destructive' },
    { text: 'Cancel', role: 'cancel' }
  ]
});

That won’t replace proper device testing, but it gives you a concrete place to start without changing every overlay in the app.

Why live updates matter for UI defects

The practical realities of release operations become evident. A safe-area regression, broken padding rule, or bad destructive button color often lives in JavaScript or CSS. If that bug ships to production, waiting on a full store release can turn a small visual defect into days of user frustration.

One practical option is a live update service for Capacitor apps. For example, Capgo delivers updated web bundles so teams can ship JavaScript, CSS, copy, config, and asset fixes without waiting for app store review, which is directly relevant when an action sheet styling or overlay bug slips past QA.

UI overlays are exactly the kind of feature where that safety net pays off. They’re highly visible, easy to break with small styling changes, and usually fixable without rebuilding native code.


If your team ships Ionic or Capacitor apps regularly, Capgo is worth evaluating as part of your release workflow. It gives you a way to push web-layer fixes for issues like action sheet layout bugs, styling regressions, and copy mistakes after release, while keeping control over rollout channels and update behavior.

Live updates for Capacitor apps

When a web-layer bug is live, ship the fix through Capgo instead of waiting days for app store approval. Users get the update in the background while native changes stay in the normal review path.

Get Started Now

Latest from our Blog

Capgo gives you the best insights you need to create a truly professional mobile app.