Skip to content

Early previewhamur is not on npm yet. The components are being built in the open, follow along on GitHub.

OverlaysReact

Alert Dialog

A dialog that requires a response to proceed.

Rendered by React 19.3.1

Loading React…
import { AlertDialog } from '@hamur/react/alert-dialog'

export default function AlertDialogExample() {
  return (
    <AlertDialog.Root>
      <AlertDialog.Trigger className="button danger">
        Delete project
      </AlertDialog.Trigger>
      <AlertDialog.Portal>
        <AlertDialog.Backdrop className="backdrop" />
        <AlertDialog.Popup className="dialog">
          <AlertDialog.Title className="dialog-title">
            Delete this project?
          </AlertDialog.Title>
          <AlertDialog.Description className="muted">
            All of its files and settings will be removed for everyone.
          </AlertDialog.Description>
          <div className="actions">
            <AlertDialog.Close className="button">
              Cancel
            </AlertDialog.Close>
            <AlertDialog.Close className="button danger">
              Delete
            </AlertDialog.Close>
          </div>
        </AlertDialog.Popup>
      </AlertDialog.Portal>
    </AlertDialog.Root>
  )
}
117Tests passing in React · 0 failing
28.5 KiBSize
1.30 msOpen
1.40 msClose
18.0 msOpenMutations
20.0 msCloseMutations
0.84 MiBMemory
productdevbook and 13 upstream authors6 commits · last

Installation

npm i @hamur/react

Not on npm yet. The package goes out under the chosen name.

Anatomy

Import the component and assemble its parts:

<AlertDialog.Root>
  <AlertDialog.Trigger />
  <AlertDialog.Portal>
    <AlertDialog.Backdrop />
    <AlertDialog.Popup>
      <AlertDialog.Title />
      <AlertDialog.Description />
      <AlertDialog.Close />
    </AlertDialog.Popup>
  </AlertDialog.Portal>
</AlertDialog.Root>

// Not in this example: AlertDialog.Viewport

Examples

Detached Triggers Simple

A trigger outside Root opens it through a handle.

AlertDialogDetachedTriggersSimple.tsx
import * as React from 'react'
import { AlertDialog } from '@hamur/react/alert-dialog'

const demoAlertDialog = AlertDialog.createHandle()

export default function AlertDialogDetachedTriggersSimpleExample() {
  return (
    <React.Fragment>
      <AlertDialog.Trigger className="button danger" handle={demoAlertDialog}>
        Discard draft
      </AlertDialog.Trigger>

      <AlertDialog.Root handle={demoAlertDialog}>
        <AlertDialog.Portal>
          <AlertDialog.Backdrop className="backdrop" />
          <AlertDialog.Popup className="dialog">
            <div className="dialog-intro">
              <AlertDialog.Title className="dialog-title">Discard draft?</AlertDialog.Title>
              <AlertDialog.Description className="muted">
                This action cannot be undone.
              </AlertDialog.Description>
            </div>
            <div className="actions">
              <AlertDialog.Close className="button">Cancel</AlertDialog.Close>
              <AlertDialog.Close className="button danger">Discard</AlertDialog.Close>
            </div>
          </AlertDialog.Popup>
        </AlertDialog.Portal>
      </AlertDialog.Root>
    </React.Fragment>
  )
}

Detached Triggers Controlled

open with triggerId lets several triggers share one alert dialog.

AlertDialogDetachedTriggersControlled.tsx
import * as React from 'react'
import { AlertDialog } from '@hamur/react/alert-dialog'

type AlertPayload = { message: string }

const demoAlertDialog = AlertDialog.createHandle<AlertPayload>()

export default function AlertDialogDetachedTriggersControlledExample() {
  const [open, setOpen] = React.useState(false)
  const [triggerId, setTriggerId] = React.useState<string | null>(null)

  const handleOpenChange = (isOpen: boolean, eventDetails: AlertDialog.Root.ChangeEventDetails) => {
    setOpen(isOpen)
    setTriggerId(eventDetails.trigger?.id ?? null)
  }

  return (
    <React.Fragment>
      <div className="button-row">
        <AlertDialog.Trigger
          className="button danger"
          handle={demoAlertDialog}
          id="alert-trigger-1"
          payload={{ message: 'Discard draft?' }}
        >
          Discard
        </AlertDialog.Trigger>
        <AlertDialog.Trigger
          className="button danger"
          handle={demoAlertDialog}
          id="alert-trigger-2"
          payload={{ message: 'Delete project?' }}
        >
          Delete
        </AlertDialog.Trigger>
        <AlertDialog.Trigger
          className="button"
          handle={demoAlertDialog}
          id="alert-trigger-3"
          payload={{ message: 'Sign out?' }}
        >
          Sign out
        </AlertDialog.Trigger>
        <button
          className="button"
          type="button"
          onClick={() => {
            setTriggerId('alert-trigger-2')
            setOpen(true)
          }}
        >
          Open programmatically
        </button>
      </div>

      <AlertDialog.Root<AlertPayload>
        handle={demoAlertDialog}
        open={open}
        onOpenChange={handleOpenChange}
        triggerId={triggerId}
      >
        {({ payload }) => (
          <AlertDialog.Portal>
            <AlertDialog.Backdrop className="backdrop" />
            <AlertDialog.Popup className="dialog">
              <div className="dialog-intro">
                <AlertDialog.Title className="dialog-title">
                  {payload?.message ?? 'Are you sure?'}
                </AlertDialog.Title>
                <AlertDialog.Description className="muted">
                  This action cannot be undone.
                </AlertDialog.Description>
              </div>
              <div className="actions">
                <AlertDialog.Close className="button">Cancel</AlertDialog.Close>
                <AlertDialog.Close className="button danger">Confirm</AlertDialog.Close>
              </div>
            </AlertDialog.Popup>
          </AlertDialog.Portal>
        )}
      </AlertDialog.Root>
    </React.Fragment>
  )
}

Open From Menu

A menu item opens the alert dialog by setting open.

AlertDialogOpenFromMenu.tsx
import * as React from 'react'
import { AlertDialog } from '@hamur/react/alert-dialog'
import { Menu } from '@hamur/react/menu'

export default function AlertDialogOpenFromMenuExample() {
  const [dialogOpen, setDialogOpen] = React.useState(false)

  return (
    <React.Fragment>
      <Menu.Root>
        <Menu.Trigger className="button">
          Playlist <CaretDownIcon />
        </Menu.Trigger>
        <Menu.Portal>
          <Menu.Positioner sideOffset={8} align="start">
            <Menu.Popup className="menu">
              <Menu.Item className="menu-item">Play</Menu.Item>
              <Menu.Item className="menu-item">Share</Menu.Item>
              <Menu.Separator className="menu-separator" />
              {/* Open the alert dialog when the menu item is clicked */}
              <Menu.Item className="menu-item" onClick={() => setDialogOpen(true)}>
                Delete…
              </Menu.Item>
            </Menu.Popup>
          </Menu.Positioner>
        </Menu.Portal>
      </Menu.Root>

      {/* Control the alert dialog state */}
      <AlertDialog.Root open={dialogOpen} onOpenChange={setDialogOpen}>
        <AlertDialog.Portal>
          <AlertDialog.Backdrop className="backdrop" />
          <AlertDialog.Popup className="dialog">
            <div className="dialog-intro">
              <AlertDialog.Title className="dialog-title">Delete playlist?</AlertDialog.Title>
              <AlertDialog.Description className="muted">
                You can't undo this action.
              </AlertDialog.Description>
            </div>
            <div className="actions">
              <AlertDialog.Close className="button">Cancel</AlertDialog.Close>
              <AlertDialog.Close className="button danger">Delete</AlertDialog.Close>
            </div>
          </AlertDialog.Popup>
        </AlertDialog.Portal>
      </AlertDialog.Root>
    </React.Fragment>
  )
}

function CaretDownIcon() {
  return (
    <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" className="icon">
      <path d="M12 6H4l4 4.5z" />
    </svg>
  )
}

API reference

19 props across 9 parts, in React syntax. className, style, render are listed once, under Shared props.

BackdropAlertDialog.Backdrop

An overlay displayed beneath the popup. Renders a <div>.

Backdrop props
PropTypeDefault
forceRenderWhether the backdrop is forced to render even when nested.booleanfalse
Backdrop data attributes
Data attributeDescription
data-openPresent when the dialog is open.
data-closedPresent when the dialog is closed.
data-starting-stylePresent when the dialog begins animating in.
data-ending-stylePresent when the dialog is animating out.

CloseAlertDialog.Close

A button that closes the dialog. Renders a <button>.

Close props
PropTypeDefault
nativeButtonWhether the component renders a native <button> element when replacing itbooleantrue
Close data attributes
Data attributeDescription
data-disabledPresent when the button is disabled.

DescriptionAlertDialog.Description

A paragraph with additional information about the dialog. Renders a <p>.

PopupAlertDialog.Popup

A container for the dialog contents. Renders a <div>.

Popup props
PropTypeDefault
initialFocusDetermines the element to focus when the dialog is opened.boolean | React.RefObject<HTMLElement | null> | ((openType: InteractionType) => boolean | void | HTMLElement | null)—
finalFocusDetermines the element to focus when the dialog is closed.boolean | React.RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | void | HTMLElement | null)—
Popup data attributes
Data attributeDescription
data-openPresent when the dialog is open.
data-closedPresent when the dialog is closed.
data-starting-stylePresent when the dialog begins animating in.
data-ending-stylePresent when the dialog is animating out.
data-nestedPresent when the dialog is nested within another dialog.
data-nested-dialog-openPresent when the dialog has other open dialogs nested within it.
Popup CSS variables
CSS variableDescription
--nested-dialogsIndicates how many dialogs are nested within.

PortalAlertDialog.Portal

A portal element that moves the popup to a different part of the DOM. By default, the portal element is appended to <body>. Renders a <div>.

Portal props
PropTypeDefault
containerA parent element to render the portal element into.HTMLElement | ShadowRoot | React.RefObject<HTMLElement | ShadowRoot | null> | null—
keepMountedWhether to keep the portal mounted in the DOM while the popup is hidden.booleanfalse

RootAlertDialog.Root

Groups all parts of the alert dialog. Doesn’t render its own element.

Root props
PropTypeDefault
defaultOpenWhether the dialog is initially open.booleanfalse
openWhether the dialog is currently open.boolean—
onOpenChangeEvent handler called when the alert dialog is opened or closed.((open: boolean, eventDetails: AlertDialogRootChangeEventDetails) => void)—
actionsRefA ref to imperative actions.React.RefObject<DialogRootActions | null>—
defaultTriggerIdID of the trigger that the dialog is associated with.string | null—
handleA handle to associate the alert dialog with a trigger.AlertDialogHandle<Payload>—
onOpenChangeCompleteEvent handler called after any animations complete when the dialog is opened or closed.((open: boolean) => void)—
triggerIdID of the trigger that the dialog is associated with.string | null—
childrenThe content of the dialog.React.ReactNode | PayloadChildRenderFunction<Payload>—

TitleAlertDialog.Title

A heading that labels the dialog. Renders a <h2>.

TriggerAlertDialog.Trigger

A button that opens the alert dialog. Renders a <button>.

Trigger props
PropTypeDefault
handleA handle to associate the trigger with an alert dialog.AlertDialogHandle<Payload>—
nativeButtonWhether the component renders a native <button> element when replacing itbooleantrue
payloadA payload to pass to the dialog when it is opened.Payload—
idID of the trigger.string—
Trigger data attributes
Data attributeDescription
data-disabledPresent when the trigger is disabled.
data-popup-openPresent when the corresponding alert dialog is open.

ViewportAlertDialog.Viewport

A positioning container for the dialog popup that can be made scrollable. Renders a <div>.

Viewport data attributes
Data attributeDescription
data-openPresent when the dialog is open.
data-closedPresent when the dialog is closed.
data-starting-stylePresent when the dialog begins animating in.
data-ending-stylePresent when the dialog is animating out.
data-nestedPresent when the dialog is nested within another dialog.
data-nested-dialog-openPresent when the dialog has other open dialogs nested within it.

Shared props

Every part that renders an element takes these.

Shared props
PropType
classNameCSS class applied to the element, or a function that returns a class based on the component's state.string | ((state: State) => string | undefined)
styleStyle applied to the element, or a function that returns a style object based on the component's state.React.CSSProperties | ((state: State) => React.CSSProperties | undefined)
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, State>

Keyboard and accessibility

Keyboard

Keyboard
KeysDoesPart
EscapeCloses the alert dialog and returns focus to the trigger that opened itPopup

Accessibility

What the parts set
PartSetsSo that
Triggeraria-haspopup="dialog", aria-expanded, aria-controlsa screen reader says the button opens a dialog and whether it is open
Popuprole="alertdialog"the popup is announced as an alert that needs an answer
Popuparia-labelledby to Title, aria-describedby to Descriptionthe question and its consequence are read when it opens
Popuparia-hidden="true" on the rest of the pagea screen reader stays inside until the person answers
Backdroprole="presentation"the overlay is not read
Viewportrole="presentation"the positioning container is not read

Focus order

  1. Opening moves focus to the first focusable element in Popup, or to initialFocus. Put the safe choice first.
  2. Tab and Shift+Tab stay inside the popup and wrap at either end.
  3. Closing returns focus to the Trigger that opened it, or to finalFocus.