ShipNative
PricingBlogFAQContact
Build my app
ShipNative
PricingBlogFAQContact
Build my app
GuideOctober 2026 · 7 min read · By Saurabh Singh

React Native Alert: Alert.alert on Both Platforms, and When to Build Your Own

Alert.alert is the one piece of dialog UI React Native gives you for free, and it is better than most people treat it: it is the real native dialog, it is accessible by default, and it needs zero styling. It also behaves differently on iOS and Android in six specific ways, cannot be awaited, and does not work on the web. This post covers all of that, with a confirm you can await, and draws the line where a custom dialog becomes the right call.

The basic call, done properly

Three arguments: title, message, and an array of buttons. A fourth options object controls dismissal on Android. Here is a delete confirmation that is correct on both platforms:

import { Alert } from 'react-native';

function confirmDelete(onConfirm: () => void) {
  Alert.alert(
    'Delete workout?',
    'This removes the workout and its 12 sets. You cannot undo this.',
    [
      // cancel: bold on iOS, negative slot on Android. Put it first.
      { text: 'Keep', style: 'cancel' },
      // destructive: red on iOS, plain on Android.
      { text: 'Delete', style: 'destructive', onPress: onConfirm },
    ],
    // Android only: block tap-outside and back-button dismissal so the
    // user has to pick. onDismiss fires if they do dismiss (cancelable: true).
    { cancelable: false },
  );
}

Three details that matter. The title should be the question and the message the consequence, in that order, because iOS truncates long titles and Android bolds them. The cancel button is listed first so iOS places it correctly; iOS reorders cancel to the left of two buttons or the bottom of a stack regardless, so the array order is about Android, where buttons render in the order given. And cancelable: false is the only way to force a choice on Android; iOS alerts can never be dismissed by tapping outside.

Where iOS and Android disagree

BehaviouriOSAndroid
Max buttonsUnlimited (3+ stack vertically)3 (positive, negative, neutral)
style: "cancel"Bold text, moved to the left or bottomMapped to the negative slot
style: "destructive"Red textNo visual change
Tap outside to dismissNeverYes unless cancelable: false
Back button dismissesN/AYes unless cancelable: false
Alert.prompt (text field)SupportedNo-op
Dark modeFollows system; userInterfaceStyle optionFollows app theme

The three-button limit is the one that bites in production. Android’s native dialog has exactly three slots, so a fourth button is silently dropped. If a decision genuinely has four outcomes it is not an alert, it is an action sheet.

The destructive style is the one that disappoints. Designers expect the red confirm from iOS everywhere; Android simply does not have the concept in its dialog. Either accept the platform look, which is what most apps do, or move to a custom dialog for the two or three destructive flows where red matters.

A confirm() you can await

Alert.alert returns nothing and calls your buttons later, which makes it awkward inside an async save handler. Wrapping it in a Promise fixes that permanently:

// confirm.ts
import { Alert } from 'react-native';

type ConfirmOptions = { title: string; message?: string; confirmText?: string; destructive?: boolean };

export function confirm({ title, message, confirmText = 'OK', destructive = false }: ConfirmOptions) {
  return new Promise<boolean>((resolve) => {
    Alert.alert(
      title,
      message,
      [
        { text: 'Cancel', style: 'cancel', onPress: () => resolve(false) },
        { text: confirmText, style: destructive ? 'destructive' : 'default', onPress: () => resolve(true) },
      ],
      // If Android dismisses via back button, resolve false instead of hanging forever.
      { cancelable: true, onDismiss: () => resolve(false) },
    );
  });
}

// usage
async function onDeleteAccount() {
  const ok = await confirm({
    title: 'Delete your account?',
    message: 'All data is removed within 24 hours.',
    confirmText: 'Delete',
    destructive: true,
  });
  if (!ok) return;
  await api.deleteAccount();
}

The onDismiss line is the part people forget. Without it, an Android user who presses back leaves your Promise pending forever and the save handler never continues. On iOS it never fires because iOS alerts cannot be dismissed, which is fine.

Alert.prompt, and what to do on Android

Alert.prompt(title, message, callbackOrButtons, type, defaultValue, keyboardType) shows an iOS alert with a text field, with types of plain-text, secure-text, and login-password. It is useful for “name this playlist” moments. It does nothing on Android, and it does not throw, so a prompt that works perfectly in the iOS simulator ships broken to half your users.

The honest options: branch on Platform.OS and show a small Modal with a TextInput on Android, use react-native-dialog which ships a cross-platform prompt that mimics each platform, or skip the prompt entirely and make the name a field on the next screen. The third option is usually the best product decision.

When to build a custom dialog instead

Stay with Alert.alertas long as you can. It is accessible, it respects the user’s text size, it handles rotation and dark mode, and it is what users expect a system question to look like. Move to a custom dialog built on Modal when one of these is true:

  • The dialog needs an image, an icon, a checkbox, or any content beyond two lines of text.
  • The red destructive button must be red on Android too.
  • The app ships to React Native Web, where Alert.alert is a no-op and shows nothing.
  • You need a text field on both platforms.
  • Brand consistency is a hard requirement, usually for a client who approved a design.

When you do build one, keep the native alert’s good habits: a title that is a question, buttons in the platform’s order, a dimmed backdrop that does not dismiss destructive confirmations, and accessibilityViewIsModal on the container so screen readers stay inside it.

Alert, toast, or sheet

The quick rule: an alert interrupts and demands an answer, a toast informs and goes away, a sheet offers choices. “Saved” is a toast. “Delete this?” is an alert. “Share via” is a sheet. Apps that use alerts for all three train users to tap through them without reading, which is exactly when the one destructive alert gets confirmed by accident.

That rule is also how apps generated with ShipNative decide: destructive and irreversible actions get the native Alert.alert with a cancel-first button order, successes get a toast, and anything with more than three choices gets a sheet. If you want a custom dialog for a specific flow, say so in the prompt and you get the Modal version with the same button semantics.

Frequently Asked Questions

How do I show an alert in React Native?

Import Alert from react-native and call Alert.alert(title, message, buttons). It opens the platform's native dialog: UIAlertController on iOS and an AlertDialog on Android. Each button is an object with text, an onPress callback, and an optional style of default, cancel, or destructive.

How many buttons can a React Native alert have?

On iOS as many as you pass; two render side by side and three or more stack vertically. On Android the native dialog has exactly three slots (positive, negative, neutral), so Alert.alert shows at most three buttons and ignores the rest. Design for three or fewer, and for more choices use an action sheet instead.

What does style: "destructive" do on Android?

Nothing visible. The destructive style turns a button red on iOS only. Android draws every alert button in the theme accent colour. If a red confirm matters on Android, you need a custom dialog built on Modal.

Does Alert.prompt work on Android?

No. Alert.prompt, which shows an alert with a text field, is iOS only. On Android it is a no-op. For a cross-platform text prompt use a small Modal with a TextInput, or a library such as react-native-dialog that implements the prompt on both platforms.

Can I await an Alert in React Native?

Not directly, Alert.alert returns undefined and fires button callbacks later. Wrap it in a Promise that resolves true or false from the button onPress handlers and you get a confirm() you can await inside an async function. The pattern is about ten lines and is included in this post.

Does Alert.alert work on React Native Web?

No. react-native-web ships Alert as an empty stub: Alert.alert() does nothing and no dialog appears, with no error. If your app also ships to the web, route through a custom dialog, or check Platform.OS === "web" and fall back to window.confirm.

→

React Native Action Sheet: Native, Expo Hook, or Custom

For more than three choices, the alert is the wrong control.

Read guide →
→

React Native Modal: Which One to Use

The container a custom dialog is built on.

Read guide →

Ship a real React Native app today

Describe, preview, and export Expo code, free to start.

Build with ShipNative
ShipNative

Build mobile apps with AI. Describe it, try it on your phone, own the code.

Features

Text to App AIApp Generator from ScreenshotPRD to Mobile AppDesign References

Tools

All free toolsApp Cost CalculatorApp Name GeneratorApp Store Keyword ToolReact Native Components

Blog

All blog postsHow to Build an App Without CodingBest AI Tools for Real Mobile AppsExpo EAS App Store ChecklistLovable, Cursor & v0 for MobileBest AI App Builders in 2026React Native AI App Builder Guide
© 2026 ShipNative
PrivacyTermsAboutFAQPricingDiscordShipNative - Featured on Startup FameFeatured on Twelve ToolsFeatured on TinyShelfShipNative - Launched on Fazier