From 0088052f158ee6e846fba12e54f7160cca0ce5a7 Mon Sep 17 00:00:00 2001 From: Karn Date: Tue, 25 Aug 2026 23:51:55 +0530 Subject: [PATCH 1/2] fix: dismiss alert dialog from backdrop --- .changeset/fuzzy-ravens-dismiss.md | 5 +++ .../src/routes/components/alert-dialog.tsx | 5 +-- .../alert-dialog/alert-dialog.test.tsx | 15 ++++++++ .../src/components/alert-dialog/index.tsx | 36 ++++++++++++++++--- 4 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 .changeset/fuzzy-ravens-dismiss.md diff --git a/.changeset/fuzzy-ravens-dismiss.md b/.changeset/fuzzy-ravens-dismiss.md new file mode 100644 index 0000000..8469d6a --- /dev/null +++ b/.changeset/fuzzy-ravens-dismiss.md @@ -0,0 +1,5 @@ +--- +"@karnstack/dowel": patch +--- + +Dismiss AlertDialog when its backdrop is clicked and restore focus to the trigger. diff --git a/apps/docs/src/routes/components/alert-dialog.tsx b/apps/docs/src/routes/components/alert-dialog.tsx index b6767d3..7492946 100644 --- a/apps/docs/src/routes/components/alert-dialog.tsx +++ b/apps/docs/src/routes/components/alert-dialog.tsx @@ -73,8 +73,9 @@ function AlertDialogDocs() {

Opening traps focus and announces both title and description. Closing - returns focus to the trigger. Use Dialog for ordinary tasks and forms; - reserve Alert Dialog for a decision that requires explicit attention. + with Escape, the backdrop, or either action returns focus to the + trigger. Use Dialog for ordinary tasks and forms; reserve Alert Dialog + for a decision that requires explicit attention.

diff --git a/packages/dowel/src/components/alert-dialog/alert-dialog.test.tsx b/packages/dowel/src/components/alert-dialog/alert-dialog.test.tsx index f200fef..58df6a1 100644 --- a/packages/dowel/src/components/alert-dialog/alert-dialog.test.tsx +++ b/packages/dowel/src/components/alert-dialog/alert-dialog.test.tsx @@ -55,6 +55,21 @@ describe("AlertDialog", () => { expect(onConfirm).toHaveBeenCalledOnce(); }); + it("dismisses on a backdrop click and returns focus", async () => { + render(); + const trigger = screen.getByRole("button", { name: "Delete repository" }); + await userEvent.click(trigger); + + const backdrop = document.querySelector( + '[data-dowel-component="alert-dialog-backdrop"]', + ); + expect(backdrop).not.toBeNull(); + await userEvent.click(backdrop!); + + await waitFor(() => expect(screen.queryByRole("alertdialog")).toBeNull()); + await waitFor(() => expect(document.activeElement).toBe(trigger)); + }); + it("carries the active theme into its portal", async () => { render( diff --git a/packages/dowel/src/components/alert-dialog/index.tsx b/packages/dowel/src/components/alert-dialog/index.tsx index ca22912..561c190 100644 --- a/packages/dowel/src/components/alert-dialog/index.tsx +++ b/packages/dowel/src/components/alert-dialog/index.tsx @@ -1,7 +1,11 @@ import { AlertDialog as BaseAlertDialog } from "@base-ui/react/alert-dialog"; +import type { + AlertDialogRootActions, + AlertDialogRootProps, +} from "@base-ui/react/alert-dialog"; import * as stylex from "@stylexjs/stylex"; -import { forwardRef, useContext } from "react"; -import type { ComponentPropsWithoutRef } from "react"; +import { createContext, forwardRef, useContext, useRef } from "react"; +import type { ComponentPropsWithoutRef, RefObject } from "react"; import { DowelThemeContext, themeStyles } from "../../theme/theme-provider"; import * as styles from "../dialog/dialog.stylex"; @@ -19,8 +23,23 @@ function partProps(style: stylex.StyleXStyles) { return { className: resolved.className, style: resolved.style }; } +const AlertDialogActionsContext = createContext< + RefObject | undefined +>(undefined); + export const AlertDialog = { - Root: BaseAlertDialog.Root, + Root: function AlertDialogRoot( + props: AlertDialogRootProps, + ) { + const fallbackActionsRef = useRef(null); + const actionsRef = props.actionsRef ?? fallbackActionsRef; + + return ( + + + + ); + }, Trigger: function AlertDialogTrigger( props: Props, @@ -52,11 +71,20 @@ export const AlertDialog = { Backdrop: function AlertDialogBackdrop( props: Props, ) { + const actionsRef = useContext(AlertDialogActionsContext); + const { onClick, ...backdropProps } = props; + return ( { + onClick?.(event); + if (!event.defaultPrevented && event.target === event.currentTarget) { + actionsRef?.current?.close(); + } + }} /> ); }, From 269a3d81bed5b7ea6f8fed9692ae856bc5f4f9f5 Mon Sep 17 00:00:00 2001 From: Karn Date: Tue, 25 Aug 2026 23:53:01 +0530 Subject: [PATCH 2/2] maint: defer alert dialog changeset --- .changeset/fuzzy-ravens-dismiss.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .changeset/fuzzy-ravens-dismiss.md diff --git a/.changeset/fuzzy-ravens-dismiss.md b/.changeset/fuzzy-ravens-dismiss.md deleted file mode 100644 index 8469d6a..0000000 --- a/.changeset/fuzzy-ravens-dismiss.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@karnstack/dowel": patch ---- - -Dismiss AlertDialog when its backdrop is clicked and restore focus to the trigger.