suiss/ui
  • Docs
  • Components
  • Patterns
  • Interactions
  • Charts
  • Blocks
GitHubGet started
DocsComponentsPatternsInteractionsChartsBlocks
Components82
  • Overview
  • Accordion
  • Alert
  • Alert Dialog
  • Alert Sheet
  • Amount Input
  • Aspect Ratio
  • Attachment
  • Avatar
  • Badge
  • Bottom Bar
  • Breadcrumb
  • Bubble
  • Button
  • Button Group
  • Calendar
  • Card
  • Carousel
  • Chart
  • Checkbox
  • Chip
  • Collapsible
  • Combobox
  • Command
  • Context Menu
  • Dialog
  • Direction
  • Drawer
  • Dropdown Menu
  • Empty
  • FAB
  • Field
  • Fullscreen Menu
  • Haptics
  • Header
  • Hover Card
  • Icon
  • Input
  • Input Accessory Bar
  • Input Group
  • Input OTP
  • Item
  • Kbd
  • Keyboard Scroll View
  • Keypad
  • Label
  • Marker
  • Menubar
  • Message
  • Message Scroller
  • Native Select
  • Navigation Menu
  • Page Indicator
  • Pagination
  • Pin Pad
  • Popover
  • Progress
  • QR Scanner
  • Questionnaire
  • Radio Group
  • Resizable
  • Screen
  • Scroll Area
  • Select
  • Separator
  • Sheet
  • Sidebar
  • Signature Pad
  • Skeleton
  • Slider
  • Spinner
  • Switch
  • Tab Bar
  • Table
  • Tabs
  • Text
  • Textarea
  • Toast
  • Toggle
  • Toggle Group
  • Tooltip
  • Wheel Picker
  • Widget

suiss/ui · Component

Alert Sheet

An action sheet that slides up from the bottom of the screen to confirm or choose an action.

Alert Sheet is an open-source component from suiss/ui for React, React Native, SwiftUI and Jetpack Compose. The @esuiss/ui CLI copies its source into your project, so you own the code.

Alert Sheet is an iOS-style action sheet built on the dialog primitives of the chosen base. An AlertSheet root contains an AlertSheetTrigger and an AlertSheetContent, which renders its own portal and scrim overlay. Inside, an AlertSheetGroup holds an optional AlertSheetHeader (with AlertSheetTitle and AlertSheetDescription) and one or more AlertSheetAction rows. A separate AlertSheetCancel sits below the group.

Use it to offer a short list of actions tied to the current context, especially on touch devices. For a centered confirmation, use Alert Dialog.

Installation

CLI

$ pnpm dlx shadcn@latest add @esuiss/alert-sheet

Works in any project with a components.json. See Installation for the one-line registry setup. Or use the suiss/ui CLI:

$ pnpm dlx @esuiss/ui@latest add alert-sheet

Manual

  1. Install the dependencies and add the cn helper:
$ pnpm add @base-ui/react class-variance-authority
  1. Add the pieces it builds on: use-scrollbar-gap.

  2. Copy the source into components/ui/alert-sheet.tsx:

  1. Update the import paths to match your project.

Usage

import { AlertSheet, AlertSheetTrigger, AlertSheetPortal, AlertSheetOverlay, AlertSheetContent, AlertSheetGroup, AlertSheetHeader, AlertSheetTitle, AlertSheetDescription, AlertSheetAction, AlertSheetCancel } 
<




















<




















<


















Every sheet needs an AlertSheetTitle for its accessible name. If the title should not be visible, hide it with className="sr-only".

By default the sheet is dismissible and behaves like a regular dialog. Set dismissible={false} to render it as an alert dialog instead.

When the sheet opens, focus moves to AlertSheetCancel unless you pass your own initialFocus to AlertSheetContent. Every action and the cancel button close the sheet. Control the open state with open and onOpenChange, or use defaultOpen.

Examples

With title

Shows a visible title above the description.

Multiple actions

Offers a default and a destructive action in one group.

Not dismissible

With dismissible={false} the sheet is an alert dialog: pressing outside does not close it, so one of the choices has to be made.

API Reference

Anatomy9 parts
<AlertSheet>
<AlertSheetTrigger />
<AlertSheetContent>
<AlertSheetGroup>
<AlertSheetHeader>
<AlertSheetTitle />
<AlertSheetDescription
01

AlertSheet

The root that manages the open state and dismissal behavior.

PropTypeDefaultDescription
dismissiblebooleantrueWhen true, renders as a dialog; when false, renders as an alert dialog.

Also accepts the props of the primitive part it wraps.

02

AlertSheetTrigger

The control that opens the sheet.

Also accepts the props of the primitive part it wraps.

03

AlertSheetPortal

Renders the sheet into a portal outside the DOM hierarchy.

Also accepts the props of the primitive part it wraps.

04

AlertSheetOverlay

The full-screen scrim behind the sheet.

Also accepts the props of the primitive part it wraps.

05

AlertSheetContent

The bottom-anchored container, rendered with its own portal and overlay.

Also accepts the props of the primitive part it wraps.

06

AlertSheetGroup

A rounded surface that stacks the header and actions with dividers.

Also accepts the props of React.ComponentProps<"div">.

07

AlertSheetHeader

A padded wrapper for the title and description.

Also accepts the props of React.ComponentProps<"div">.

08

AlertSheetTitle

The heading that names the sheet.

Also accepts the props of the primitive part it wraps.

09

AlertSheetDescription

Supporting text that describes the sheet.

Also accepts the props of the primitive part it wraps.

10

AlertSheetAction

A full-width row button that performs an action and closes the sheet.

PropTypeDefaultDescription
variant"default" | "destructive""default"Text color of the action. default uses text-primary; destructive uses text-destructive.

Also accepts the props of the primitive part it wraps.

11

AlertSheetCancel

A separate, bold button that dismisses the sheet.

Also accepts the props of the primitive part it wraps.

Accessibility

  • With dismissible (the default) the popup has the dialog role; with dismissible={false} it has the alertdialog role.
  • The popup is named by AlertSheetTitle and described by AlertSheetDescription.
  • Focus moves to the cancel button on open and returns to the trigger on close.
  • Escape closes the sheet.

Notes

  • Styling hooks: data-slot values from alert-sheet through alert-sheet-cancel.
  • The sheet slides up from translate-y-full as it opens and back down as it closes, and its bottom padding respects env(safe-area-inset-bottom).
  • When an action contains an icon, the group switches to a grid so icons and labels align across rows.

AlertSheet presents a set of choices related to the current context. AlertSheetContent renders through a portal (inside a full-window overlay on iOS), slides up from the bottom over a dimmed scrim, and respects the safe-area insets.

Actions sit in an AlertSheetGroup, a rounded card that draws a hairline between each child, and AlertSheetCancel sits below it as its own card with bold text. Use it for two to a few actions; for a single decision that needs confirmation, use Alert Dialog.

Installation

$ pnpm dlx shadcn@latest add @esuiss-rn/alert-sheet

See Installation for the components.json setup. Or use the suiss/ui CLI:

$ pnpm dlx @esuiss/ui@latest add alert-sheet
$ pnpm dlx @esuiss/ui@latest add alert-sheet --platform swift
NextAmount InputPreviousAlert Dialog

On this page

  1. 01Installation
    • CLI
    • Manual
  2. 02Usage
  3. 03Examples
    • With title
    • Multiple actions
    • Not dismissible
  4. 04API Reference
    • AlertSheet
    • AlertSheetTrigger
    • AlertSheetPortal
    • AlertSheetOverlay
    • AlertSheetContent
    • AlertSheetGroup
    • AlertSheetHeader
    • AlertSheetTitle
    • AlertSheetDescription
    • AlertSheetAction
    • AlertSheetCancel
  5. 05Accessibility
  6. 06Notes
  7. 07Installation
    • Manual
  8. 08Usage
  9. 09Examples
    • Default
    • Not dismissible
  10. 10API Reference
    • AlertSheet
    • AlertSheetOverlay
    • AlertSheetContent
    • AlertSheetGroup
    • AlertSheetHeader
    • AlertSheetTitle
    • AlertSheetDescription
    • AlertSheetAction
    • AlertSheetCancel
  11. 11Accessibility
  12. 12Notes
from
"@/components/ui/alert-sheet"
div
className
=
"flex min-h-svh items-center justify-center"
>
<AlertSheet>
<AlertSheetTrigger render={<Button variant="outline" />}>
Delete order
</AlertSheetTrigger>
<AlertSheetContent>
<AlertSheetGroup>
<AlertSheetHeader>
<AlertSheetTitle className="sr-only">Delete order</AlertSheetTitle>
<AlertSheetDescription>
Order #1042 will be permanently deleted. This action cannot be
undone.
</AlertSheetDescription>
</AlertSheetHeader>
<AlertSheetAction variant="destructive">
Delete Order
</AlertSheetAction>
</AlertSheetGroup>
<AlertSheetCancel>Cancel</AlertSheetCancel>
</AlertSheetContent>
</AlertSheet>
</div>
div
className
=
"flex min-h-svh items-center justify-center"
>
<AlertSheet>
<AlertSheetTrigger asChild>
<Button variant="outline">Delete order</Button>
</AlertSheetTrigger>
<AlertSheetContent>
<AlertSheetGroup>
<AlertSheetHeader>
<AlertSheetTitle className="sr-only">Delete order</AlertSheetTitle>
<AlertSheetDescription>
Order #1042 will be permanently deleted. This action cannot be
undone.
</AlertSheetDescription>
</AlertSheetHeader>
<AlertSheetAction variant="destructive">
Delete Order
</AlertSheetAction>
</AlertSheetGroup>
<AlertSheetCancel>Cancel</AlertSheetCancel>
</AlertSheetContent>
</AlertSheet>
</div>
div
className
=
"flex min-h-svh items-center justify-center"
>
<AlertSheet>
<Button variant="outline">Delete order</Button>
<AlertSheetContent>
<AlertSheetGroup>
<AlertSheetHeader>
<AlertSheetTitle className="sr-only">Delete order</AlertSheetTitle>
<AlertSheetDescription>
Order #1042 will be permanently deleted. This action cannot be
undone.
</AlertSheetDescription>
</AlertSheetHeader>
<AlertSheetAction variant="destructive">
Delete Order
</AlertSheetAction>
</AlertSheetGroup>
<AlertSheetCancel>Cancel</AlertSheetCancel>
</AlertSheetContent>
</AlertSheet>
</div>
/>
</AlertSheetHeader>
<AlertSheetAction />
</AlertSheetGroup>
<AlertSheetCancel />
</AlertSheetContent>
</AlertSheet>
$ pnpm dlx @esuiss/ui@latest add alert-sheet --platform kotlin

The CLI also adds what it builds on: Icon, Text. On React Native it also adds the animated view components/ui/animated-view.tsx.

Manual

  1. Install the dependencies:
npx expo install class-variance-authority cn react-native-reanimated react-native-safe-area-context react-native-screens react-native-worklets
  1. Add the pieces it builds on: Icon, Text, and the primitives components/primitives/alert-dialog.tsx, components/primitives/dialog.tsx, components/primitives/use-render.tsx, and the animated view components/ui/animated-view.tsx from the next step.

  2. Copy the animated view into components/ui/animated-view.tsx:

uniwind/components/ui/animated-view.tsx
import { Pressable } from "react-native"
import Animated from "react-native-reanimated"

const AnimatedPressable = Animated.createAnimatedComponent(Pressable)

type AnimatedViewProps =
  | (React.ComponentProps<typeof Animated.View> &
      React.RefAttributes<React.ComponentRef<typeof Animated.View>> & {
        as?: "View"
      })
  | (React.ComponentProps<typeof AnimatedPressable> &
      React.RefAttributes<React.ComponentRef<typeof AnimatedPressable>> & {
        as: "Pressable"
      })

function AnimatedView(props: AnimatedViewProps) {
  1. Copy the source into components/ui/alert-sheet.tsx:
uniwind/components/ui/alert-sheet.tsx
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"
import * as React from "react"
import { Platform, View, type ViewProps } from "react-native"
import {
  Easing,
  FadeIn,
  FadeOut,
  ReduceMotion,
  SlideInDown,
  SlideOutDown,
} from "react-native-reanimated"
import { useSafeAreaInsets } from "react-native-safe-area-context"
import { FullWindowOverlay as RNFullWindowOverlay } from "react-native-screens"
import * as AlertSheetPrimitive from "@/components/primitives/alert-dialog"
import * as DialogPrimitive from "@/components/primitives/dialog"
  1. Update the import paths to match your project.
  1. Install the dependencies:
npx expo install class-variance-authority cn react-native-reanimated react-native-safe-area-context react-native-screens react-native-worklets
  1. Add the pieces it builds on: Icon, Text, and the primitives components/primitives/alert-dialog.tsx, components/primitives/dialog.tsx, components/primitives/use-render.tsx, and the animated view components/ui/animated-view.tsx from the next step.

  2. Copy the animated view into components/ui/animated-view.tsx:

  1. Copy the source into components/ui/alert-sheet.tsx:
  1. Update the import paths to match your project.
  1. Install the dependencies:
npx expo install react-native-reanimated react-native-safe-area-context react-native-screens react-native-worklets
  1. Add the pieces it builds on: Icon, Text, and the primitives components/primitives/alert-dialog.tsx, components/primitives/dialog.tsx, components/primitives/use-render.tsx, and the animated view components/ui/animated-view.tsx from the next step.

  2. Copy the animated view into components/ui/animated-view.tsx:

  1. Copy the source into components/ui/alert-sheet.tsx:
  1. Update the import paths to match your project.
  1. Target iOS 17 or later.

  2. Add the Swift package to your app:

Package.swift
.package(url: "https://github.com/phosphor-icons/swift", from: "2.1.0")
  1. Copy the theme files every view uses:
swift/Theme/Theme.swift
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
  }
swift/Theme/Tokens.swift
import SwiftUI

public enum EsuissColor {
  public static let background = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x000000))
  public static let foreground = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
  public static let card = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x1D1D1F))
  public static let cardForeground = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
  public static let popover = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x1D1D1F))
  public static let popoverForeground = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
  public static let primary = Color.adaptive(light: .init(0x0071E3), dark: .init(0x2997FF))
  public static let primaryForeground = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0xFFFFFF))
  public static let secondary = Color.adaptive(light: .init(0x000000, 0.04), dark: .init(0xFFFFFF, 0.1))
  public static let secondaryForeground = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
  public static let muted = Color.adaptive(light: .init(0xF5F5F7), dark: .init(0x161617))
  public static let mutedForeground = Color.adaptive(light: .init(0x6E6E73), dark: .init(0xA1A1A6))
  public static let accent = Color.adaptive(light: .init(0x000000, 0.06), dark: .init(0xFFFFFF, 0.1))
  1. Copy the source and every file it uses:
swift/Components/AlertSheet.swift
import SwiftUI

extension View {
  public func alertSheet<Content: View>(
    isPresented: Binding<Bool>,
    dismissible: Bool = true,
    @ViewBuilder content: @escaping () -> Content
  ) -> some View {
    modifier(
      CoverPresenter(
        isPresented: isPresented,
        enter: .timingCurve(0.32, 0.72, 0, 1, duration: 0.3),
        exit: .timingCurve(0.32, 0.72, 0, 1, duration: 0.25)
      ) { visible in
        AlertSheetStage(visible: visible, dismissible: dismissible, content: content)
      }
swift/Components/Button.swift
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: 44
swift/Components/ButtonGroup.swift
import 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
}
swift/Components/Overlay.swift
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()
  }
}
swift/Components/TextVariant.swift
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
    }
  }
  1. Add the files to your app target.
  1. Set minSdk to 24 or higher and add the dependencies:
build.gradle.kts
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")
  1. Copy the theme files every view uses:
kotlin/theme/Theme.kt
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.DpOffset
kotlin/theme/Tokens.kt
package 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 background: Color,
    val foreground: Color,
    val card: Color,
    val cardForeground: Color,
    val popover: Color,
    val popoverForeground: Color,
    val primary: Color,
    val primaryForeground: Color,
  1. Copy the source and every file it uses:
kotlin/components/AlertSheet.kt
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.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
kotlin/components/Button.kt
package 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.RoundedCornerShape
kotlin/components/ButtonGroup.kt
package 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.Immutable
kotlin/components/Content.kt
package 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 }
kotlin/components/FormSupport.kt
package com.esuiss.ui.components

import android.provider.Settings
import androidx.compose.animation.core.CubicBezierEasing
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.text.selection.TextSelectionColors
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
kotlin/components/Icon.kt
package 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,
kotlin/components/Overlay.kt
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
  1. Update the com.esuiss.ui package names to match your project.

Usage

import { AlertSheet, AlertSheetOverlay, AlertSheetContent, AlertSheetGroup, AlertSheetHeader, AlertSheetTitle, AlertSheetDescription, AlertSheetAction, AlertSheetCancel } from "@/components/ui/alert-sheet"
<View className="items-start">
  <AlertSheet defaultOpen>
    <AlertSheetTrigger render={<Button variant="outline" />}>
      <Text>Show sheet</Text>
    </AlertSheetTrigger>
    <SheetBody />
  </AlertSheet>
</View>

Compose AlertSheetTrigger and AlertSheetContent. Inside the content, put an AlertSheetGroup with an optional AlertSheetHeader (AlertSheetTitle, AlertSheetDescription) followed by AlertSheetActions, then an AlertSheetCancel.

Every action closes the sheet when pressed. String children are wrapped in Text automatically. Use variant="destructive" on an action that deletes or removes, and disabled to dim and block it.

By default the sheet is dismissible and a tap on the scrim closes it. Set dismissible={false} to require an explicit choice.

Examples

Default

iOS
Alert Sheet on iOS in light modeAlert Sheet on iOS in dark mode
import { View } from "react-native"
import { AlertSheet, AlertSheetAction, AlertSheetCancel, AlertSheetContent, AlertSheetDescription, AlertSheetGroup, AlertSheetHeader, AlertSheetTitle, AlertSheetTrigger } from "@/components/ui/alert-sheet"
import { Button } from "@/components/ui/button"
import { Text } from "@/components/ui/text"

function SheetBody() {
  return (
    <AlertSheetContent>
      <AlertSheetGroup>
        <AlertSheetHeader>
          <AlertSheetTitle>Delete this photo?</AlertSheetTitle>
          <AlertSheetDescription>
            This photo will be deleted from all of your devices.
          </AlertSheetDescription>
        </AlertSheetHeader>
        <AlertSheetAction>Save to Files</AlertSheetAction>
        <AlertSheetAction variant="destructive">Delete Photo</AlertSheetAction>
      </AlertSheetGroup>
      <AlertSheetCancel>Cancel</AlertSheetCancel>
    </AlertSheetContent>
  )
}

export function AlertSheetExample() {
  return (
    <View className="items-start">
      <AlertSheet>
        <AlertSheetTrigger render={<Button variant="outline" />}>
          <Text>Show sheet</Text>
        </AlertSheetTrigger>
        <SheetBody />
      </AlertSheet>
    </View>
  )
}
import EsuissUI
import SwiftUI

struct AlertSheetExample: View {
  var body: some View {
    AlertSheetDemo(title: "Show sheet") {
      AlertSheetBody()
    }
  }
}

struct AlertSheetDemo<Body: View>: View {
  let title: LocalizedStringKey
  let dismissible: Bool
  let content: () -> Body

  @State private var open: Bool

  init(
    title: LocalizedStringKey,
    open: Bool = false,
    dismissible: Bool = true,
    @ViewBuilder content: @escaping () -> Body
  ) {
    self.title = title
    self.dismissible = dismissible
    self.content = content
    _open = State(initialValue: open)
  }

  var body: some View {
    Button(title) { open = true }
      .buttonStyle(.variant(.outline))
      .alertSheet(isPresented: $open, dismissible: dismissible, content: content)
  }
}

struct AlertSheetBody: View {
  var body: some View {
    AlertSheetContent {
      AlertSheetGroup {
        AlertSheetHeader {
          AlertSheetTitle("Delete this photo?")
          AlertSheetDescription("This photo will be deleted from all of your devices.")
        }
        AlertSheetAction("Save to Files")
        AlertSheetAction("Delete Photo", variant: .destructive)
      }
      AlertSheetCancel("Cancel")
    }
  }
}
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.AlertSheet
import com.esuiss.ui.components.AlertSheetAction
import com.esuiss.ui.components.AlertSheetActionVariant
import com.esuiss.ui.components.AlertSheetCancel
import com.esuiss.ui.components.AlertSheetDescription
import com.esuiss.ui.components.AlertSheetGroup
import com.esuiss.ui.components.AlertSheetHeader
import com.esuiss.ui.components.AlertSheetTitle
import com.esuiss.ui.components.Button
import com.esuiss.ui.components.ButtonVariant

@Composable
fun AlertSheetExample() {
    AlertSheetDefault(defaultOpen = false)
}

@Composable
fun AlertSheetDefault(defaultOpen: Boolean) {
    var open by rememberSaveable { mutableStateOf(defaultOpen) }
    Column {
        Button("Show sheet", onClick = { open = true }, variant = ButtonVariant.Outline)
        AlertSheet(open = open, onOpenChange = { open = it }) {
            AlertSheetGroup {
                AlertSheetHeader {
                    AlertSheetTitle("Delete this photo?")
                    AlertSheetDescription("This photo will be deleted from all of your devices.")
                }
                AlertSheetAction("Save to Files", onClick = {})
                AlertSheetAction("Delete Photo", onClick = {}, variant = AlertSheetActionVariant.Destructive)
            }
            AlertSheetCancel("Cancel")
        }
    }
}

Not dismissible

dismissible={false} with a destructive and a disabled action.

iOS
Alert Sheet (not dismissible) on iOS in light modeAlert Sheet (not dismissible) on iOS in dark mode
import { View } from "react-native"
import { AlertSheet, AlertSheetAction, AlertSheetCancel, AlertSheetContent, AlertSheetGroup, AlertSheetHeader, AlertSheetTitle, AlertSheetTrigger } from "@/components/ui/alert-sheet"
import { Button } from "@/components/ui/button"
import { Text } from "@/components/ui/text"

export function AlertSheetNotDismissibleExample() {
  return (
    <View className="items-start">
      <AlertSheet dismissible={false}>
        <AlertSheetTrigger render={<Button variant="outline" />}>
          <Text>Sign out</Text>
        </AlertSheetTrigger>
        <AlertSheetContent>
          <AlertSheetGroup>
            <AlertSheetHeader>
              <AlertSheetTitle>Sign out of all devices?</AlertSheetTitle>
            </AlertSheetHeader>
            <AlertSheetAction variant="destructive">Sign Out</AlertSheetAction>
            <AlertSheetAction disabled>Keep Signed In Here</AlertSheetAction>
          </AlertSheetGroup>
          <AlertSheetCancel>Cancel</AlertSheetCancel>
        </AlertSheetContent>
      </AlertSheet>
    </View>
  )
}
import EsuissUI
import SwiftUI

struct AlertSheetNotDismissibleExample: View {
  var body: some View {
    AlertSheetDemo(title: "Sign out", dismissible: false) {
      AlertSheetContent {
        AlertSheetGroup {
          AlertSheetHeader {
            AlertSheetTitle("Sign out of all devices?")
          }
          AlertSheetAction("Sign Out", variant: .destructive)
          AlertSheetAction("Keep Signed In Here")
            .disabled(true)
        }
        AlertSheetCancel("Cancel")
      }
    }
  }
}

struct AlertSheetDemo<Body: View>: View {
  let title: LocalizedStringKey
  let dismissible: Bool
  let content: () -> Body

  @State private var open: Bool

  init(
    title: LocalizedStringKey,
    open: Bool = false,
    dismissible: Bool = true,
    @ViewBuilder content: @escaping () -> Body
  ) {
    self.title = title
    self.dismissible = dismissible
    self.content = content
    _open = State(initialValue: open)
  }

  var body: some View {
    Button(title) { open = true }
      .buttonStyle(.variant(.outline))
      .alertSheet(isPresented: $open, dismissible: dismissible, 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.AlertSheet
import com.esuiss.ui.components.AlertSheetAction
import com.esuiss.ui.components.AlertSheetActionVariant
import com.esuiss.ui.components.AlertSheetCancel
import com.esuiss.ui.components.AlertSheetGroup
import com.esuiss.ui.components.AlertSheetHeader
import com.esuiss.ui.components.AlertSheetTitle
import com.esuiss.ui.components.Button
import com.esuiss.ui.components.ButtonVariant

@Composable
fun AlertSheetNotDismissible() {
    var open by rememberSaveable { mutableStateOf(false) }
    Column {
        Button("Sign out", onClick = { open = true }, variant = ButtonVariant.Outline)
        AlertSheet(open = open, onOpenChange = { open = it }, dismissible = false) {
            AlertSheetGroup {
                AlertSheetHeader {
                    AlertSheetTitle("Sign out of all devices?")
                }
                AlertSheetAction("Sign Out", onClick = {}, variant = AlertSheetActionVariant.Destructive)
                AlertSheetAction("Keep Signed In Here", onClick = {}, enabled = false)
            }
            AlertSheetCancel("Cancel")
        }
    }
}

API Reference

01

AlertSheet

Root that holds the open state.

PropTypeDefaultDescription
dismissiblebooleantrueWhen false, the scrim no longer closes the sheet. Defaults to true.

Also accepts the props of AlertSheetPrimitive.RootProps.

02

AlertSheetOverlay

Dimmed scrim behind the sheet.

Also accepts the props of React.ComponentProps<typeof AlertSheetPrimitive.Backdrop>.

03

AlertSheetContent

Bottom-anchored container that slides in and out.

PropTypeDefaultDescription
portalHoststring-Name of the portal host to render into.

Also accepts the props of React.ComponentProps<typeof AlertSheetPrimitive.Popup>.

04

AlertSheetGroup

Rounded card that separates its children with hairlines.

Also accepts the props of ViewProps.

05

AlertSheetHeader

Centered block for the title and description.

Also accepts the props of ViewProps.

06

AlertSheetTitle

Small, bold heading for the sheet.

Also accepts the props of React.ComponentProps<typeof AlertSheetPrimitive.Title>.

07

AlertSheetDescription

Supporting text below the title.

Also accepts the props of React.ComponentProps<typeof AlertSheetPrimitive.Description>.

08

AlertSheetAction

Full-width action row that closes the sheet.

PropTypeDefaultDescription
variant"default" | "destructive""default"Text color of the action.

Also accepts the props of AlertSheetActionProps.

09

AlertSheetCancel

Separate cancel card with bold text.

Also accepts the props of AlertSheetActionProps.

Accessibility

  • When dismissible the popup has the dialog role, otherwise alertdialog; it is labelled by the title and marked as modal for VoiceOver.
  • Focus moves to the title when the sheet opens.
  • Actions have the button role with a disabled state.
  • Separators between actions are hidden from screen readers.

Notes

  • Padding uses the bottom and side safe-area insets, with a minimum of 8 points.
  • On iOS the content is wrapped in a full-window overlay.
  • Animations respect the system Reduce Motion setting.
  • Uses bg-popover, text-primary and text-destructive tokens, with opacity-80 while pressed; light and dark follow the system appearance.
iOS
Alert Sheet (open by default) on iOS in light modeAlert Sheet (open by default) on iOS in dark mode
import { View } from "react-native"
import { AlertSheet, AlertSheetAction, AlertSheetCancel, AlertSheetContent, AlertSheetDescription, AlertSheetGroup, AlertSheetHeader, AlertSheetTitle, AlertSheetTrigger } from "@/components/ui/alert-sheet"
import { Button } from "@/components/ui/button"
import { Text } from "@/components/ui/text"

function SheetBody() {
  return (
    <AlertSheetContent>
      <AlertSheetGroup>
        <AlertSheetHeader>
          <AlertSheetTitle>Delete this photo?</AlertSheetTitle>
          <AlertSheetDescription>
            This photo will be deleted from all of your devices.
          </AlertSheetDescription>
        </AlertSheetHeader>
        <AlertSheetAction>Save to Files</AlertSheetAction>
        <AlertSheetAction variant="destructive">Delete Photo</AlertSheetAction>
      </AlertSheetGroup>
      <AlertSheetCancel>Cancel</AlertSheetCancel>
    </AlertSheetContent>
  )
}

export function AlertSheetOpenByDefaultExample() {
  return (
    <View className="items-start">
      <AlertSheet defaultOpen>
        <AlertSheetTrigger render={<Button variant="outline" />}>
          <Text>Show sheet</Text>
        </AlertSheetTrigger>
        <SheetBody />
      </AlertSheet>
    </View>
  )
}
import EsuissUI
import SwiftUI

struct AlertSheetOpenByDefaultExample: View {
  var body: some View {
    AlertSheetDemo(title: "Show sheet", open: true) {
      AlertSheetBody()
    }
  }
}

struct AlertSheetDemo<Body: View>: View {
  let title: LocalizedStringKey
  let dismissible: Bool
  let content: () -> Body

  @State private var open: Bool

  init(
    title: LocalizedStringKey,
    open: Bool = false,
    dismissible: Bool = true,
    @ViewBuilder content: @escaping () -> Body
  ) {
    self.title = title
    self.dismissible = dismissible
    self.content = content
    _open = State(initialValue: open)
  }

  var body: some View {
    Button(title) { open = true }
      .buttonStyle(.variant(.outline))
      .alertSheet(isPresented: $open, dismissible: dismissible, content: content)
  }
}

struct AlertSheetBody: View {
  var body: some View {
    AlertSheetContent {
      AlertSheetGroup {
        AlertSheetHeader {
          AlertSheetTitle("Delete this photo?")
          AlertSheetDescription("This photo will be deleted from all of your devices.")
        }
        AlertSheetAction("Save to Files")
        AlertSheetAction("Delete Photo", variant: .destructive)
      }
      AlertSheetCancel("Cancel")
    }
  }
}
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.AlertSheet
import com.esuiss.ui.components.AlertSheetAction
import com.esuiss.ui.components.AlertSheetActionVariant
import com.esuiss.ui.components.AlertSheetCancel
import com.esuiss.ui.components.AlertSheetDescription
import com.esuiss.ui.components.AlertSheetGroup
import com.esuiss.ui.components.AlertSheetHeader
import com.esuiss.ui.components.AlertSheetTitle
import com.esuiss.ui.components.Button
import com.esuiss.ui.components.ButtonVariant

@Composable
fun AlertSheetOpenByDefaultExample() {
    AlertSheetDefault(defaultOpen = true)
}

@Composable
fun AlertSheetDefault(defaultOpen: Boolean) {
    var open by rememberSaveable { mutableStateOf(defaultOpen) }
    Column {
        Button("Show sheet", onClick = { open = true }, variant = ButtonVariant.Outline)
        AlertSheet(open = open, onOpenChange = { open = it }) {
            AlertSheetGroup {
                AlertSheetHeader {
                    AlertSheetTitle("Delete this photo?")
                    AlertSheetDescription("This photo will be deleted from all of your devices.")
                }
                AlertSheetAction("Save to Files", onClick = {})
                AlertSheetAction("Delete Photo", onClick = {}, variant = AlertSheetActionVariant.Destructive)
            }
            AlertSheetCancel("Cancel")
        }
    }
}
import {
  AlertSheet,
  AlertSheetAction,
  AlertSheetCancel,
  AlertSheetContent,
  AlertSheetDescription,
  AlertSheetGroup,
  AlertSheetHeader,
  AlertSheetTitle,
  AlertSheetTrigger,
} from "@/components/ui/alert-sheet"
import { Button } from "@/components/ui/button"

export default function AlertSheetDemo() {
  return (
    <div className="flex min-h-svh items-center justify-center">
      <AlertSheet>
        <AlertSheetTrigger render={<Button variant="outline" />}>
          Delete order
        </AlertSheetTrigger>
        <AlertSheetContent>
          <AlertSheetGroup>
            <AlertSheetHeader>
              <AlertSheetTitle className="sr-only">
                Delete order
              </AlertSheetTitle>
              <AlertSheetDescription>
                Order #1042 will be permanently deleted. This action cannot be
                undone.
              </AlertSheetDescription>
            </AlertSheetHeader>
            <AlertSheetAction variant="destructive">
              Delete Order
            </AlertSheetAction>
          </AlertSheetGroup>
          <AlertSheetCancel>Cancel</AlertSheetCancel>
        </AlertSheetContent>
      </AlertSheet>
    </div>
  )
}
components/ui/alert-sheet.tsx
"use client"

import { AlertDialog as AlertSheetPrimitive } from "@base-ui/react/alert-dialog"
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"
import { cva, type VariantProps } from "class-variance-authority"
import * as React from "react"
import { useScrollbarGap } from "@/hooks/use-scrollbar-gap"
import { cn } from "@/lib/utils"

function AlertSheet({
  dismissible = true,
  ...props
}: AlertSheetPrimitive.Root.Props & { dismissible?: boolean }) {
  if (dismissible) {
    return <DialogPrimitive.Root data-slot="alert-sheet" {...props} />
  }
import {
  AlertSheet,
  AlertSheetAction,
  AlertSheetCancel,
  AlertSheetContent,
  AlertSheetDescription,
  AlertSheetGroup,
  AlertSheetHeader,
  AlertSheetTitle,
  AlertSheetTrigger,
} from "@/components/ui/alert-sheet"
import { Button } from "@/components/ui/button"

export default function AlertSheetWithTitle() {
  return (
    <div className="flex min-h-svh items-center justify-center">
      <AlertSheet>
        <AlertSheetTrigger render={<Button variant="outline" />}>
          Sign out
        </AlertSheetTrigger>
        <AlertSheetContent>
          <AlertSheetGroup>
            <AlertSheetHeader>
              <AlertSheetTitle>Sign out of your account?</AlertSheetTitle>
              <AlertSheetDescription>
                You will need to sign in again to access your projects.
              </AlertSheetDescription>
            </AlertSheetHeader>
            <AlertSheetAction variant="destructive">Sign Out</AlertSheetAction>
          </AlertSheetGroup>
          <AlertSheetCancel>Cancel</AlertSheetCancel>
        </AlertSheetContent>
      </AlertSheet>
    </div>
  )
}
import {
  AlertSheet,
  AlertSheetAction,
  AlertSheetCancel,
  AlertSheetContent,
  AlertSheetGroup,
  AlertSheetHeader,
  AlertSheetTitle,
  AlertSheetTrigger,
} from "@/components/ui/alert-sheet"
import { Button } from "@/components/ui/button"

export default function AlertSheetMultipleActions() {
  return (
    <div className="flex min-h-svh items-center justify-center">
      <AlertSheet>
        <AlertSheetTrigger render={<Button variant="outline" />}>
          Close draft
        </AlertSheetTrigger>
        <AlertSheetContent>
          <AlertSheetGroup>
            <AlertSheetHeader>
              <AlertSheetTitle>Save changes to this draft?</AlertSheetTitle>
            </AlertSheetHeader>
            <AlertSheetAction>Save Draft</AlertSheetAction>
            <AlertSheetAction variant="destructive">
              Delete Draft
            </AlertSheetAction>
          </AlertSheetGroup>
          <AlertSheetCancel>Cancel</AlertSheetCancel>
        </AlertSheetContent>
      </AlertSheet>
    </div>
  )
}
import {
  AlertSheet,
  AlertSheetAction,
  AlertSheetCancel,
  AlertSheetContent,
  AlertSheetDescription,
  AlertSheetGroup,
  AlertSheetHeader,
  AlertSheetTitle,
  AlertSheetTrigger,
} from "@/components/ui/alert-sheet"
import { Button } from "@/components/ui/button"

export default function AlertSheetNotDismissible() {
  return (
    <div className="flex min-h-svh items-center justify-center">
      <AlertSheet dismissible={false}>
        <AlertSheetTrigger render={<Button variant="outline" />}>
          Delete order
        </AlertSheetTrigger>
        <AlertSheetContent>
          <AlertSheetGroup>
            <AlertSheetHeader>
              <AlertSheetTitle className="sr-only">
                Delete order
              </AlertSheetTitle>
              <AlertSheetDescription>
                Order #1042 will be permanently deleted. This action cannot be
                undone.
              </AlertSheetDescription>
            </AlertSheetHeader>
            <AlertSheetAction variant="destructive">
              Delete Order
            </AlertSheetAction>
          </AlertSheetGroup>
          <AlertSheetCancel>Cancel</AlertSheetCancel>
        </AlertSheetContent>
      </AlertSheet>
    </div>
  )
}
nativewind/components/ui/animated-view.tsx
import { Pressable } from "react-native"
import Animated from "react-native-reanimated"

const AnimatedPressable = Animated.createAnimatedComponent(Pressable)

type AnimatedViewProps =
  | (React.ComponentProps<typeof Animated.View> &
      React.RefAttributes<React.ComponentRef<typeof Animated.View>> & {
        as?: "View"
      })
  | (React.ComponentProps<typeof AnimatedPressable> &
      React.RefAttributes<React.ComponentRef<typeof AnimatedPressable>> & {
        as: "Pressable"
      })

function AnimatedView(props: AnimatedViewProps) {
nativewind/components/ui/alert-sheet.tsx
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"
import * as React from "react"
import { Platform, View, type ViewProps } from "react-native"
import {
  Easing,
  FadeIn,
  FadeOut,
  ReduceMotion,
  SlideInDown,
  SlideOutDown,
} from "react-native-reanimated"
import { useSafeAreaInsets } from "react-native-safe-area-context"
import { FullWindowOverlay as RNFullWindowOverlay } from "react-native-screens"
import * as AlertSheetPrimitive from "@/components/primitives/alert-dialog"
import * as DialogPrimitive from "@/components/primitives/dialog"
stylesheet/components/ui/animated-view.tsx
import { Pressable } from "react-native"
import Animated from "react-native-reanimated"

const AnimatedPressable = Animated.createAnimatedComponent(Pressable)

type AnimatedViewProps =
  | (React.ComponentProps<typeof Animated.View> &
      React.RefAttributes<React.ComponentRef<typeof Animated.View>> & {
        as?: "View"
      })
  | (React.ComponentProps<typeof AnimatedPressable> &
      React.RefAttributes<React.ComponentRef<typeof AnimatedPressable>> & {
        as: "Pressable"
      })

function AnimatedView(props: AnimatedViewProps) {
stylesheet/components/ui/alert-sheet.tsx
import * as React from "react"
import {
  Platform,
  Pressable,
  StyleSheet,
  type TextStyle,
  View,
  type ViewProps,
} from "react-native"
import {
  Easing,
  FadeIn,
  FadeOut,
  ReduceMotion,
  SlideInDown,
  SlideOutDown,