suiss/uim · React Native Component
Alert Dialog
A modal dialog that interrupts the user with an important message and asks for a response.
Alert Dialog is an open-source mobile component from suiss/uim for React Native and Expo, with SwiftUI and Jetpack Compose versions built from the same design tokens. The @esuiss/uim CLI copies its source into your project, so you own the code.


import { View } from "react-native"
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"
import { Text } from "@/components/ui/text"
function AlertDialogBody() {
return (
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete your
account and remove your data from our servers.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>
<Text>Cancel</Text>
</AlertDialogCancel>
<AlertDialogAction>
<Text>Continue</Text>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
)
}
export function AlertDialogOpenByDefaultExample() {
return (
<View className="items-start">
<AlertDialog defaultOpen>
<AlertDialogTrigger render={<Button variant="outline" />}>
<Text>Show dialog</Text>
</AlertDialogTrigger>
<AlertDialogBody />
</AlertDialog>
</View>
)
}import EsuissUI
import SwiftUI
struct AlertDialogOpenByDefaultExample: View {
var body: some View {
AlertDialogDemo(title: "Show dialog", variant: .outline, open: true) {
AlertDialogBody()
}
}
}
struct AlertDialogDemo<Body: View>: View {
let title: LocalizedStringKey
let variant: VariantButtonStyle.Variant
let content: () -> Body
@State private var open: Bool
init(
title: LocalizedStringKey,
variant: VariantButtonStyle.Variant,
open: Bool = false,
@ViewBuilder content: @escaping () -> Body
) {
self.title = title
self.variant = variant
self.content = content
_open = State(initialValue: open)
}
var body: some View {
Button(title) { open = true }
.buttonStyle(.variant(variant))
.alertDialog(isPresented: $open, content: content)
}
}
struct AlertDialogBody: View {
var body: some View {
AlertDialogContent {
AlertDialogHeader {
AlertDialogTitle("Are you absolutely sure?")
AlertDialogDescription(
"This action cannot be undone. This will permanently delete your account and remove your data from our servers."
)
}
AlertDialogFooter {
AlertDialogCancel()
AlertDialogAction("Continue")
}
}
}
}import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import com.esuiss.ui.components.AlertDialog
import com.esuiss.ui.components.AlertDialogAction
import com.esuiss.ui.components.AlertDialogCancel
import com.esuiss.ui.components.AlertDialogDescription
import com.esuiss.ui.components.AlertDialogFooter
import com.esuiss.ui.components.AlertDialogHeader
import com.esuiss.ui.components.AlertDialogTitle
import com.esuiss.ui.components.Button
import com.esuiss.ui.components.ButtonVariant
@Composable
fun AlertDialogOpenByDefaultExample() {
AlertDialogDefault(defaultOpen = true)
}
@Composable
fun AlertDialogDefault(defaultOpen: Boolean) {
var open by rememberSaveable { mutableStateOf(defaultOpen) }
Column {
Button("Show dialog", onClick = { open = true }, variant = ButtonVariant.Outline)
AlertDialog(open = open, onOpenChange = { open = it }) {
AlertDialogHeader {
AlertDialogTitle("Are you absolutely sure?")
AlertDialogDescription(
"This action cannot be undone. This will permanently delete your account and remove your data from our servers.",
)
}
AlertDialogFooter {
AlertDialogCancel("Cancel")
AlertDialogAction("Continue", onClick = {})
}
}
}
}AlertDialog is a modal that cannot be dismissed by tapping the scrim. AlertDialogContent renders through a portal (inside a full-window overlay on iOS) with a dimmed scrim and a centered card that scales and fades in.
Use it for confirmations that need an explicit choice, such as deleting data. For less critical content that can be dismissed freely, use Dialog; for a list of choices anchored to the bottom of the screen, use Alert Sheet.
Installation
$ pnpm dlx @esuiss/uim@latest add alert-dialog$ pnpm dlx @esuiss/uim@latest add alert-dialog --platform swift$ pnpm dlx @esuiss/uim@latest add alert-dialog --platform kotlinThe CLI also adds what it builds on: Button, Icon, Text.
Manual
- Install the dependencies:
npx expo install cn react-native-reanimated react-native-screens react-native-worklets-
Add the pieces it builds on: Button, Icon, Text, and the primitives
components/primitives/alert-dialog.tsx,components/primitives/use-render.tsx. -
Copy the source into
components/ui/alert-dialog.tsx:
import { cn } from "cn"
import * as React from "react"
import { Platform, View, type ViewProps } from "react-native"
import {
FadeIn,
FadeOut,
Keyframe,
ReduceMotion,
} from "react-native-reanimated"
import { FullWindowOverlay as RNFullWindowOverlay } from "react-native-screens"
import * as AlertDialogPrimitive from "@/components/primitives/alert-dialog"
import { resolveClassName } from "@/components/primitives/use-render"
import { AnimatedView } from "@/components/ui/animated-view"
import { Button } from "@/components/ui/button"
import { IconClassContext } from "@/components/ui/icon"
import { TextClassContext } from "@/components/ui/text"- Update the import paths to match your project.
- Install the dependencies:
npx expo install cn react-native-reanimated react-native-screens react-native-worklets-
Add the pieces it builds on: Button, Icon, Text, and the primitives
components/primitives/alert-dialog.tsx,components/primitives/use-render.tsx. -
Copy the source into
components/ui/alert-dialog.tsx:
import { cn } from "cn"
import * as React from "react"
import { Platform, View, type ViewProps } from "react-native"
import {
FadeIn,
FadeOut,
Keyframe,
ReduceMotion,
} from "react-native-reanimated"
import { FullWindowOverlay as RNFullWindowOverlay } from "react-native-screens"
import * as AlertDialogPrimitive from "@/components/primitives/alert-dialog"
import { resolveClassName } from "@/components/primitives/use-render"
import { AnimatedView } from "@/components/ui/animated-view"
import { Button } from "@/components/ui/button"
import { IconClassContext } from "@/components/ui/icon"
import { TextClassContext } from "@/components/ui/text"- Update the import paths to match your project.
- Install the dependencies:
npx expo install react-native-reanimated react-native-screens react-native-worklets-
Add the pieces it builds on: Button, Icon, Text, and the primitives
components/primitives/alert-dialog.tsx,components/primitives/use-render.tsx. -
Copy the source into
components/ui/alert-dialog.tsx:
import * as React from "react"
import { Platform, StyleSheet, View, type ViewProps } from "react-native"
import {
FadeIn,
FadeOut,
Keyframe,
ReduceMotion,
} from "react-native-reanimated"
import { FullWindowOverlay as RNFullWindowOverlay } from "react-native-screens"
import * as AlertDialogPrimitive from "@/components/primitives/alert-dialog"
import { AnimatedView } from "@/components/ui/animated-view"
import { Button } from "@/components/ui/button"
import { TextStyleContext } from "@/components/ui/text"
import { fontSize, radius, shadow, useTheme } from "@/components/ui/theme"
const AlertDialog = AlertDialogPrimitive.Root- Update the import paths to match your project.
-
Target iOS 17 or later.
-
Add the Swift package to your app:
.package(url: "https://github.com/phosphor-icons/swift", from: "2.1.0")- Copy the theme files every view uses:
import SwiftUI
import UIKit
struct RGBA: Sendable {
let red: CGFloat
let green: CGFloat
let blue: CGFloat
let alpha: CGFloat
init(_ hex: UInt32, _ alpha: CGFloat = 1) {
red = CGFloat((hex >> 16) & 0xFF) / 255
green = CGFloat((hex >> 8) & 0xFF) / 255
blue = CGFloat(hex & 0xFF) / 255
self.alpha = alpha
}import SwiftUI
extension Color {
public static let surface = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x000000))
public static let surfaceSecondary = Color.adaptive(light: .init(0xF5F5F7), dark: .init(0x161617))
public static let surfaceTertiary = Color.adaptive(light: .init(0xE8E8ED), dark: .init(0x2C2C2E))
public static let surfaceRaised = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x1D1D1F))
public static let label = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
public static let labelSecondary = Color.adaptive(light: .init(0x6E6E73), dark: .init(0xA1A1A6))
public static let labelTertiary = Color.adaptive(light: .init(0x86868B), dark: .init(0x86868B))
public static let labelQuaternary = Color.adaptive(light: .init(0xD2D2D7), dark: .init(0x424245))
public static let control = Color.adaptive(light: .init(0x000000, 0.04), dark: .init(0xFFFFFF, 0.1))
public static let controlPressed = Color.adaptive(light: .init(0x000000, 0.12), dark: .init(0xFFFFFF, 0.22))
public static let controlSelected = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
public static let controlDisabled = Color.adaptive(light: .init(0x000000, 0.03), dark: .init(0xFFFFFF, 0.05))
public static let onControlSelected = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x000000))- Copy the source and every file it uses:
import SwiftUI
extension View {
public func alertDialog<Content: View>(
isPresented: Binding<Bool>,
@ViewBuilder content: @escaping () -> Content
) -> some View {
modifier(
CoverPresenter(isPresented: isPresented) { visible in
CenteredStage(visible: visible, dismissible: false, content: content)
}
)
}
}
public struct AlertDialogContent<Content: View>: View {import SwiftUI
public struct VariantButtonStyle: ButtonStyle {
public enum Variant: CaseIterable, Sendable {
case `default`, outline, secondary, tinted, ghost, plain, destructive, link
}
public enum Size: CaseIterable, Sendable {
case `default`, xs, sm, lg, xl, icon, iconXs, iconSm, iconLg, iconXl
var height: CGFloat {
switch self {
case .xs, .iconXs: 24
case .sm, .iconSm: 32
case .default, .icon: 36
case .lg, .iconLg: 44import SwiftUI
extension EnvironmentValues {
@Entry var buttonGroup: Axis? = nil
@Entry var buttonGroupSize: CGSize = .zero
}
enum ButtonGroupSpace: Hashable {
case group
}
struct ButtonGroupFrame {
let bounds: Anchor<CGRect>
let outlined: Bool
}import PhosphorSwift
import SwiftUI
extension Shadow {
static let control = Shadow(color: .black.opacity(0.08), radius: 0.5, y: 0.5)
static let thumb = Shadow(color: .black.opacity(0.12), radius: 1.5, y: 1)
}
struct SelectionIndicator<Mark: View>: View {
let checked: Bool
let pressed: Bool
let cornerRadius: CGFloat
let mark: Mark
@Environment(\.isEnabled) private var isEnabled
@Environment(\.isInvalid) private var invalidimport SwiftUI
extension View {
public func dialog<Content: View>(
isPresented: Binding<Bool>,
@ViewBuilder content: @escaping () -> Content
) -> some View {
modifier(
CoverPresenter(isPresented: isPresented) { visible in
CenteredStage(visible: visible, dismissible: true, content: content)
}
)
}
}
struct CenteredStage<Content: View>: View {import SwiftUI
extension EnvironmentValues {
@Entry public var isInvalid = false
@Entry var fieldInset = false
@Entry var fieldGroupInset = false
@Entry var fieldSetInset = false
}
extension View {
public func invalid(_ isInvalid: Bool = true) -> some View {
environment(\.isInvalid, isInvalid)
}
}
public enum FieldOrientation: CaseIterable, Sendable {import SwiftUI
extension EnvironmentValues {
@Entry public var hapticsEnabled = true
}
extension View {
public func haptics(_ enabled: Bool) -> some View {
environment(\.hapticsEnabled, enabled)
}
func hapticFeedback<Trigger: Equatable>(_ feedback: SensoryFeedback, trigger: Trigger) -> some View {
modifier(HapticFeedbackModifier(feedback: feedback, trigger: trigger) { _, _ in true })
}
func hapticFeedback<Trigger: Equatable>(import PhosphorSwift
import Observation
import SwiftUI
public enum PopupAlignment: Sendable, CaseIterable {
case start, center, end
}
struct OverlayDismiss: Sendable {
let action: @MainActor @Sendable () -> Void
@MainActor func callAsFunction() {
action()
}
}import SwiftUI
public struct Separator: View {
let orientation: Axis
public init(_ orientation: Axis = .horizontal) {
self.orientation = orientation
}
public var body: some View {
Rectangle()
.fill(Color.separator)
.frame(width: orientation == .vertical ? 1 : nil, height: orientation == .horizontal ? 1 : nil)
.frame(maxWidth: orientation == .horizontal ? .infinity : nil, maxHeight: orientation == .vertical ? .infinity : nil)
.accessibilityHidden(true)
}import SwiftUI
public enum TextVariant: CaseIterable, Sendable {
case `default`, h1, h2, h3, h4, p, blockquote, code, lead, large, small, muted
var style: TextStyle {
switch self {
case .h1: .xl3
case .h2: .xl2
case .h3: .xl
case .h4, .lead, .large: .lg
case .code, .small, .muted: .sm
default: .base
}
}- Add the files to your app target.
- Set
minSdkto 24 or higher and add the dependencies:
implementation(platform("androidx.compose:compose-bom:2026.09.00"))
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.ui:ui")
implementation("com.adamglin:phosphor-icon-android:1.0.0")- Copy the theme files every view uses:
package com.esuiss.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.dropShadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.shadow.Shadow as DropShadow
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.LineHeightStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffsetpackage com.esuiss.ui.theme
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
@Immutable
data class Colors(
val surface: Color,
val surfaceSecondary: Color,
val surfaceTertiary: Color,
val surfaceRaised: Color,
val label: Color,
val labelSecondary: Color,
val labelTertiary: Color,
val labelQuaternary: Color,- Copy the source and every file it uses:
package com.esuiss.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composablepackage com.esuiss.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShapepackage com.esuiss.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.Immutablepackage com.esuiss.ui.components
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
val LocalContentStyle = compositionLocalOf { TextStyle.Default }
val LocalContentColor = compositionLocalOf { Color.Unspecified }
val LocalIconSize = compositionLocalOf { 20.dp }package com.esuiss.ui.components
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidthpackage com.esuiss.ui.components
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.Dp
import com.esuiss.ui.theme.EsuissTheme
@Composable
fun Icon(
imageVector: ImageVector,package com.esuiss.ui.components
import android.os.Build
import android.view.Window
import android.view.WindowManager
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.EnterExitState
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.animation.slideIn- Update the
com.esuiss.uipackage names to match your project.
Usage
import { AlertDialogOverlay, AlertDialogContent, AlertDialogHeader, AlertDialogFooter, AlertDialogMedia, AlertDialogTitle, AlertDialogDescription, AlertDialogAction, AlertDialogCancel } from "@/components/ui/alert-dialog"<View className="items-start">
<AlertDialog defaultOpen>
<AlertDialogTrigger render={<Button variant="outline" />}>
<Text>Show dialog</Text>
</AlertDialogTrigger>
<AlertDialogBody />
</AlertDialog>
</View>Compose AlertDialogTrigger, then AlertDialogContent with an AlertDialogHeader (AlertDialogTitle, AlertDialogDescription) and an AlertDialogFooter holding AlertDialogCancel and AlertDialogAction. Both footer buttons close the dialog and stretch to equal widths inside the footer.
AlertDialogAction and AlertDialogCancel accept Button's variant and size; use variant="destructive" for destructive confirmations. Add AlertDialogMedia above the title for an icon badge.
Use open and onOpenChange for controlled state, or defaultOpen for uncontrolled. Pass portalHost to render into a named portal host.
Examples
Default


import { View } from "react-native"
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"
import { Text } from "@/components/ui/text"
function AlertDialogBody() {
return (
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete your
account and remove your data from our servers.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>
<Text>Cancel</Text>
</AlertDialogCancel>
<AlertDialogAction>
<Text>Continue</Text>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
)
}
export function AlertDialogExample() {
return (
<View className="items-start">
<AlertDialog>
<AlertDialogTrigger render={<Button variant="outline" />}>
<Text>Show dialog</Text>
</AlertDialogTrigger>
<AlertDialogBody />
</AlertDialog>
</View>
)
}import EsuissUI
import SwiftUI
struct AlertDialogExample: View {
var body: some View {
AlertDialogDemo(title: "Show dialog", variant: .outline) {
AlertDialogBody()
}
}
}
struct AlertDialogDemo<Body: View>: View {
let title: LocalizedStringKey
let variant: VariantButtonStyle.Variant
let content: () -> Body
@State private var open: Bool
init(
title: LocalizedStringKey,
variant: VariantButtonStyle.Variant,
open: Bool = false,
@ViewBuilder content: @escaping () -> Body
) {
self.title = title
self.variant = variant
self.content = content
_open = State(initialValue: open)
}
var body: some View {
Button(title) { open = true }
.buttonStyle(.variant(variant))
.alertDialog(isPresented: $open, content: content)
}
}
struct AlertDialogBody: View {
var body: some View {
AlertDialogContent {
AlertDialogHeader {
AlertDialogTitle("Are you absolutely sure?")
AlertDialogDescription(
"This action cannot be undone. This will permanently delete your account and remove your data from our servers."
)
}
AlertDialogFooter {
AlertDialogCancel()
AlertDialogAction("Continue")
}
}
}
}import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import com.esuiss.ui.components.AlertDialog
import com.esuiss.ui.components.AlertDialogAction
import com.esuiss.ui.components.AlertDialogCancel
import com.esuiss.ui.components.AlertDialogDescription
import com.esuiss.ui.components.AlertDialogFooter
import com.esuiss.ui.components.AlertDialogHeader
import com.esuiss.ui.components.AlertDialogTitle
import com.esuiss.ui.components.Button
import com.esuiss.ui.components.ButtonVariant
@Composable
fun AlertDialogExample() {
AlertDialogDefault(defaultOpen = false)
}
@Composable
fun AlertDialogDefault(defaultOpen: Boolean) {
var open by rememberSaveable { mutableStateOf(defaultOpen) }
Column {
Button("Show dialog", onClick = { open = true }, variant = ButtonVariant.Outline)
AlertDialog(open = open, onOpenChange = { open = it }) {
AlertDialogHeader {
AlertDialogTitle("Are you absolutely sure?")
AlertDialogDescription(
"This action cannot be undone. This will permanently delete your account and remove your data from our servers.",
)
}
AlertDialogFooter {
AlertDialogCancel("Cancel")
AlertDialogAction("Continue", onClick = {})
}
}
}
}Small
The narrower sm content size.


import { View } from "react-native"
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"
import { Text } from "@/components/ui/text"
export function AlertDialogSmallExample() {
return (
<View className="items-start">
<AlertDialog>
<AlertDialogTrigger render={<Button variant="outline" />}>
<Text>Discard draft</Text>
</AlertDialogTrigger>
<AlertDialogContent size="sm">
<AlertDialogHeader>
<AlertDialogTitle>Discard draft?</AlertDialogTitle>
<AlertDialogDescription>
Your unsaved message will be lost.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>
<Text>Keep</Text>
</AlertDialogCancel>
<AlertDialogAction variant="destructive">
<Text>Discard</Text>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</View>
)
}import EsuissUI
import SwiftUI
struct AlertDialogSmallExample: View {
var body: some View {
AlertDialogDemo(title: "Discard draft", variant: .outline) {
AlertDialogContent(size: .sm) {
AlertDialogHeader {
AlertDialogTitle("Discard draft?")
AlertDialogDescription("Your unsaved message will be lost.")
}
AlertDialogFooter {
AlertDialogCancel("Keep")
AlertDialogAction("Discard", variant: .destructive)
}
}
}
}
}
struct AlertDialogDemo<Body: View>: View {
let title: LocalizedStringKey
let variant: VariantButtonStyle.Variant
let content: () -> Body
@State private var open: Bool
init(
title: LocalizedStringKey,
variant: VariantButtonStyle.Variant,
open: Bool = false,
@ViewBuilder content: @escaping () -> Body
) {
self.title = title
self.variant = variant
self.content = content
_open = State(initialValue: open)
}
var body: some View {
Button(title) { open = true }
.buttonStyle(.variant(variant))
.alertDialog(isPresented: $open, content: content)
}
}import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import com.esuiss.ui.components.AlertDialog
import com.esuiss.ui.components.AlertDialogAction
import com.esuiss.ui.components.AlertDialogCancel
import com.esuiss.ui.components.AlertDialogDescription
import com.esuiss.ui.components.AlertDialogFooter
import com.esuiss.ui.components.AlertDialogHeader
import com.esuiss.ui.components.AlertDialogSize
import com.esuiss.ui.components.AlertDialogTitle
import com.esuiss.ui.components.Button
import com.esuiss.ui.components.ButtonVariant
@Composable
fun AlertDialogSmall() {
var open by rememberSaveable { mutableStateOf(false) }
Column {
Button("Discard draft", onClick = { open = true }, variant = ButtonVariant.Outline)
AlertDialog(open = open, onOpenChange = { open = it }, size = AlertDialogSize.Sm) {
AlertDialogHeader {
AlertDialogTitle("Discard draft?")
AlertDialogDescription("Your unsaved message will be lost.")
}
AlertDialogFooter {
AlertDialogCancel("Keep")
AlertDialogAction("Discard", onClick = {}, variant = ButtonVariant.Destructive)
}
}
}
}With media
An icon badge above the title with AlertDialogMedia.


import { TrashIcon } from "phosphor-react-native"
import { View } from "react-native"
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogMedia, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Text } from "@/components/ui/text"
export function AlertDialogWithMediaExample() {
return (
<View className="items-start">
<AlertDialog>
<AlertDialogTrigger render={<Button variant="destructive" />}>
<Text>Delete project</Text>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogMedia className="bg-danger/10">
<Icon as={TrashIcon} className="text-danger" />
</AlertDialogMedia>
<AlertDialogTitle>Delete this project?</AlertDialogTitle>
<AlertDialogDescription>
This permanently removes the project and all of its files. This
action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>
<Text>Cancel</Text>
</AlertDialogCancel>
<AlertDialogAction variant="destructive">
<Text>Delete</Text>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</View>
)
}import EsuissUI
import PhosphorSwift
import SwiftUI
struct AlertDialogWithMediaExample: View {
var body: some View {
AlertDialogDemo(title: "Delete project", variant: .destructive) {
AlertDialogContent {
AlertDialogHeader {
AlertDialogMedia {
Ph.trash.regular
.foregroundStyle(Color.danger)
}
AlertDialogTitle("Delete this project?")
AlertDialogDescription(
"This permanently removes the project and all of its files. This action cannot be undone."
)
}
AlertDialogFooter {
AlertDialogCancel()
AlertDialogAction("Delete", variant: .destructive)
}
}
}
}
}
struct AlertDialogDemo<Body: View>: View {
let title: LocalizedStringKey
let variant: VariantButtonStyle.Variant
let content: () -> Body
@State private var open: Bool
init(
title: LocalizedStringKey,
variant: VariantButtonStyle.Variant,
open: Bool = false,
@ViewBuilder content: @escaping () -> Body
) {
self.title = title
self.variant = variant
self.content = content
_open = State(initialValue: open)
}
var body: some View {
Button(title) { open = true }
.buttonStyle(.variant(variant))
.alertDialog(isPresented: $open, content: content)
}
}import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.Trash
import com.esuiss.ui.components.AlertDialog
import com.esuiss.ui.components.AlertDialogAction
import com.esuiss.ui.components.AlertDialogCancel
import com.esuiss.ui.components.AlertDialogDescription
import com.esuiss.ui.components.AlertDialogFooter
import com.esuiss.ui.components.AlertDialogHeader
import com.esuiss.ui.components.AlertDialogMedia
import com.esuiss.ui.components.AlertDialogTitle
import com.esuiss.ui.components.Button
import com.esuiss.ui.components.ButtonVariant
import com.esuiss.ui.components.Icon
import com.esuiss.ui.theme.EsuissTheme
@Composable
fun AlertDialogWithMedia() {
var open by rememberSaveable { mutableStateOf(false) }
val colors = EsuissTheme.colors
Column {
Button("Delete project", onClick = { open = true }, variant = ButtonVariant.Destructive)
AlertDialog(open = open, onOpenChange = { open = it }) {
AlertDialogHeader {
AlertDialogMedia {
Box(
Modifier
.matchParentSize()
.background(colors.surfaceRaised, CircleShape)
.background(colors.danger.copy(alpha = 0.1f), CircleShape),
)
Icon(PhosphorIcons.Regular.Trash, contentDescription = null, tint = colors.danger)
}
AlertDialogTitle("Delete this project?")
AlertDialogDescription(
"This permanently removes the project and all of its files. This action cannot be undone.",
)
}
AlertDialogFooter {
AlertDialogCancel("Cancel")
AlertDialogAction("Delete", onClick = {}, variant = ButtonVariant.Destructive)
}
}
}
}Destructive
A destructive action button for an irreversible confirmation.


import { View } from "react-native"
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"
import { Text } from "@/components/ui/text"
export function AlertDialogDestructiveExample() {
return (
<View className="items-start">
<AlertDialog>
<AlertDialogTrigger render={<Button variant="destructive" />}>
<Text>Delete project</Text>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete project?</AlertDialogTitle>
<AlertDialogDescription>
All deployments and settings for this project will be removed.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>
<Text>Cancel</Text>
</AlertDialogCancel>
<AlertDialogAction variant="destructive">
<Text>Delete</Text>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</View>
)
}import EsuissUI
import SwiftUI
struct AlertDialogDestructiveExample: View {
var body: some View {
AlertDialogDemo(title: "Delete project", variant: .destructive) {
AlertDialogContent {
AlertDialogHeader {
AlertDialogTitle("Delete project?")
AlertDialogDescription("All deployments and settings for this project will be removed.")
}
AlertDialogFooter {
AlertDialogCancel()
AlertDialogAction("Delete", variant: .destructive)
}
}
}
}
}
struct AlertDialogDemo<Body: View>: View {
let title: LocalizedStringKey
let variant: VariantButtonStyle.Variant
let content: () -> Body
@State private var open: Bool
init(
title: LocalizedStringKey,
variant: VariantButtonStyle.Variant,
open: Bool = false,
@ViewBuilder content: @escaping () -> Body
) {
self.title = title
self.variant = variant
self.content = content
_open = State(initialValue: open)
}
var body: some View {
Button(title) { open = true }
.buttonStyle(.variant(variant))
.alertDialog(isPresented: $open, content: content)
}
}import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import com.esuiss.ui.components.AlertDialog
import com.esuiss.ui.components.AlertDialogAction
import com.esuiss.ui.components.AlertDialogCancel
import com.esuiss.ui.components.AlertDialogDescription
import com.esuiss.ui.components.AlertDialogFooter
import com.esuiss.ui.components.AlertDialogHeader
import com.esuiss.ui.components.AlertDialogTitle
import com.esuiss.ui.components.Button
import com.esuiss.ui.components.ButtonVariant
@Composable
fun AlertDialogDestructive() {
var open by rememberSaveable { mutableStateOf(false) }
Column {
Button("Delete project", onClick = { open = true }, variant = ButtonVariant.Destructive)
AlertDialog(open = open, onOpenChange = { open = it }) {
AlertDialogHeader {
AlertDialogTitle("Delete project?")
AlertDialogDescription("All deployments and settings for this project will be removed.")
}
AlertDialogFooter {
AlertDialogCancel("Cancel")
AlertDialogAction("Delete", onClick = {}, variant = ButtonVariant.Destructive)
}
}
}
}API Reference
AlertDialogOverlay
Dimmed scrim behind the dialog.
React.ComponentProps<typeof AlertDialogPrimitive.Backdrop>AlertDialogContent
Centered card with scale and fade transitions.
React.ComponentProps<typeof AlertDialogPrimitive.Popup>- About
- Name of the portal host to render into.
- Type
string- Default
- —
- About
defaultorsmmaximum width.- Type
"default""sm"- Default
- "default"
AlertDialogHeader
Centered column for the title and description.
ViewPropsAlertDialogFooter
Row of actions with equal-width buttons.
ViewPropsAlertDialogMedia
Round badge for an icon above the title.
ViewPropsAlertDialogTitle
Dialog heading.
React.ComponentProps<typeof AlertDialogPrimitive.Title>AlertDialogDescription
Supporting text below the title.
React.ComponentProps<typeof AlertDialogPrimitive.Description>AlertDialogAction
Button that confirms and closes the dialog.
React.ComponentProps<typeof AlertDialogPrimitive.Close>React.ComponentProps<typeof Button>AlertDialogCancel
Secondary button that closes the dialog.
React.ComponentProps<typeof AlertDialogPrimitive.Close>React.ComponentProps<typeof Button>Accessibility
- The popup has the
alertdialogrole, is labelled by the title and is marked as modal for VoiceOver. - Focus moves to the title when the dialog opens.
- Tapping the scrim does not close it; the Android back button and the VoiceOver escape gesture do.
- The title has the
headingrole, and the action buttons have thebuttonrole.
Notes
- On iOS the content is wrapped in a full-window overlay so it appears above native navigation.
- Enter and exit animations respect the system Reduce Motion setting.
- Uses
bg-surface-raised,bg-scrimand label tokens; light and dark follow the system appearance.