Alert dialog

Free · MITReact Native · Expo

A centered alert that springs in from slightly larger; touch an action and slide across the others with a tick per action, tap outside and it nudges, and async actions keep it open with a spinner until they settle.

View as Markdown
Try it on your phone
Scan to open this demo in your phone's browser and use it with your fingers.

Installation

$ npx shadcn@latest add https://goodui.dev/native/r/alert-dialog.json

Then run npx expo install --fix so react-native-gesture-handler, react-native-reanimated match your Expo SDK. Files land in components/goodui-native/alert-dialog.

  • Wrap the app in GestureHandlerRootView and ThemeProvider (the alert renders in its overlay layer).

Usage

import { AlertDialog } from "@/components/goodui-native/alert-dialog/alert-dialog";

export function CancelPlan({ open, onOpenChange }: { open: boolean; onOpenChange: (open: boolean) => void }) {
  return (
    <AlertDialog
      open={open}
      onOpenChange={onOpenChange}
      tone="warning"
      title="Cancel your Pro plan?"
      message="You keep Pro features until Dec 1."
      actions={[
        { label: "Keep Pro", style: "cancel" },
        { label: "Cancel plan", style: "destructive", onPress: () => api.cancelPlan() },
      ]}
    />
  );
}

When to use

Use it for
  • A decision the person must make before continuing: sign out everywhere, cancel a plan, leave without saving.
  • Asking for one short value, such as a new name.
  • A confirmation whose request can fail: return the promise and the alert shows pending and error itself.
Reach for something else
  • Deleting items: confirm in place with hold-to-confirm or offer Undo through toast.
  • Several unrelated choices: use action-sheet.
  • Telling someone an action worked: use the button's own success state or a toast.
  • Forms with more than one field: use bottom-sheet.

Examples

With a text field

<AlertDialog
  open={open}
  onOpenChange={setOpen}
  title="Rename workout"
  input={{ defaultValue: workout.name, placeholder: "Workout name" }}
  errorLabel="Give the workout a name."
  actions={[
    { label: "Cancel", style: "cancel" },
    { label: "Save", onPress: (name) => (name.trim() ? api.rename(workout.id, name) : Promise.reject()) },
  ]}
/>

Filled buttons, three actions stack

<AlertDialog
  open={open}
  onOpenChange={setOpen}
  variant="filled"
  title="Save changes to “Q3 report”?"
  actions={[
    { label: "Save", onPress: save },
    { label: "Do not save", style: "destructive" },
    { label: "Cancel", style: "cancel" },
  ]}
/>

API reference

PropTypeDefault
open*
Whether the alert is shown. Closing animates out before it unmounts.
boolean—
onOpenChange*
Called with false after an action runs, or when the alert is dismissed.
(open: boolean) => void—
title*
One short question or statement.
string—
message
What happens and why, in one or two sentences.
string—
actions
Default: a single "OK". Two actions sit side by side; three or more stack.
AlertAction[]—
actionsLayout
`auto` puts two short actions side by side and stacks everything else.
"auto" | "row" | "stacked"—
variant
`plain` is the platform alert with hairline-separated text actions; `filled` uses rounded buttons.
"plain" | "filled"—
tone
Adds an icon badge above the title for a status.
"default" | "danger" | "warning" | "success"—
input
Adds a text field, such as a name or a password.
AlertInput—
dismissible
Allow closing by tapping outside or pressing back. Default `false`: an alert needs an answer, and a tap outside nudges it.
boolean—
errorLabel
Shown when an action's promise rejects. Same as `labels.error`.
string—
haptic
Haptics on commit and on async results. Default `true`.
boolean—
labels
Overrides for the built-in strings; a GooduiLocaleProvider pack sets them app-wide.
Partial<AlertDialogLabels>—
testID
Identifier for end-to-end tests.
string—

Motion and haptics

  • Enter: scales from 1.1 to 1 on the bouncy spring while it fades in; exit fades and shrinks to 0.96.
  • Actions follow the finger: the highlight moves to whichever action is under it, and releasing outside every action chooses nothing.
  • Tap outside a non-dismissible alert: it nudges to 1.035 and springs back on the bouncy spring.
  • Async failure: the card shakes and the error appears; filled buttons also scale to 0.97 under the finger.
  • With a text field the card rises by half the keyboard height so it stays centered in the free space (native).
  • Reduced motion: no scale-in, nudge, shake or button scale; the alert fades in and out.
  • Haptic: tap when an action is chosen
  • Haptic: select each time the finger slides onto another action
  • Haptic: success when an action's promise resolves, error when it rejects
  • Highlight and hit testing run on the UI thread; the alert mounts only while open or closing.

Accessibility

  • The card has accessibilityRole alert and is modal; the title and message are announced when it opens.
  • Each action is a button with busy and disabled states; double tap activates it. On the web actions are focusable buttons (Enter or Space).
  • A failed action announces errorLabel and shows it under the message.
  • Android back runs the cancel action when there is one (or closes a dismissible alert); otherwise it nudges.

Notes for AI

  • Label actions with verbs; mark the safe way out with style 'cancel' and the risky one with 'destructive'.
  • Return a promise from onPress to keep the alert open with a spinner; it closes on success and shakes with errorLabel on failure. Do not add your own loading state.
  • onPress receives the text field's value when input is set.
  • Alerts are not dismissible by default; pass dismissible for purely informational alerts.
  • Two short actions (14 characters or fewer) sit side by side; set actionsLayout to force a row or a stack.