Skip to content

Commit a3ff079

Browse files
feat: add AlertDialog component (#699)
feat: add AlertDialog component using Base UI primitive Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 952be2d commit a3ff079

11 files changed

Lines changed: 882 additions & 0 deletions

File tree

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
'use client';
2+
3+
import { AlertDialog, Button, Flex } from '@raystack/apsara';
4+
import PlaygroundLayout from './playground-layout';
5+
6+
export function AlertDialogExamples() {
7+
return (
8+
<PlaygroundLayout title='AlertDialog'>
9+
<Flex gap='medium'>
10+
<AlertDialog>
11+
<AlertDialog.Trigger render={<Button color='danger' />}>
12+
Delete Item
13+
</AlertDialog.Trigger>
14+
<AlertDialog.Content width='400px'>
15+
<AlertDialog.Header>
16+
<AlertDialog.Title>Are you sure?</AlertDialog.Title>
17+
</AlertDialog.Header>
18+
<AlertDialog.Body>
19+
<AlertDialog.Description>
20+
This action cannot be undone. This will permanently delete the
21+
item from our servers.
22+
</AlertDialog.Description>
23+
</AlertDialog.Body>
24+
<AlertDialog.Footer>
25+
<AlertDialog.Close
26+
render={
27+
<Button variant='outline' color='neutral'>
28+
Cancel
29+
</Button>
30+
}
31+
/>
32+
<Button color='danger'>Delete</Button>
33+
</AlertDialog.Footer>
34+
</AlertDialog.Content>
35+
</AlertDialog>
36+
<AlertDialog>
37+
<AlertDialog.Trigger render={<Button variant='outline' />}>
38+
Discard Changes
39+
</AlertDialog.Trigger>
40+
<AlertDialog.Content width={400} showCloseButton={false}>
41+
<AlertDialog.Body>
42+
<AlertDialog.Title>Unsaved Changes</AlertDialog.Title>
43+
<AlertDialog.Description>
44+
You have unsaved changes. Do you want to discard them?
45+
</AlertDialog.Description>
46+
</AlertDialog.Body>
47+
<AlertDialog.Footer>
48+
<AlertDialog.Close
49+
render={
50+
<Button variant='outline' color='neutral'>
51+
Continue Editing
52+
</Button>
53+
}
54+
/>
55+
<Button color='danger'>Discard</Button>
56+
</AlertDialog.Footer>
57+
</AlertDialog.Content>
58+
</AlertDialog>
59+
</Flex>
60+
</PlaygroundLayout>
61+
);
62+
}

apps/www/src/components/playground/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export * from './alert-dialog-examples';
12
export * from './amount-examples';
23
export * from './announcement-bar-examples';
34
export * from './avatar-examples';
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
'use client';
2+
3+
export const getCode = (props: { title?: string; description?: string }) => {
4+
const { title, description } = props;
5+
return `
6+
<AlertDialog>
7+
<AlertDialog.Trigger render={<Button color="danger" />}>
8+
Discard draft
9+
</AlertDialog.Trigger>
10+
<AlertDialog.Content width={400} showCloseButton={false}>
11+
<AlertDialog.Body>
12+
<AlertDialog.Title>${title}</AlertDialog.Title>
13+
<AlertDialog.Description>
14+
${description}
15+
</AlertDialog.Description>
16+
</AlertDialog.Body>
17+
<AlertDialog.Footer>
18+
<AlertDialog.Close render={<Button variant="outline" color="neutral">Cancel</Button>} />
19+
<AlertDialog.Close render={<Button color="danger">Discard</Button>} />
20+
</AlertDialog.Footer>
21+
</AlertDialog.Content>
22+
</AlertDialog>`;
23+
};
24+
25+
export const playground = {
26+
type: 'playground',
27+
controls: {
28+
title: { type: 'text', initialValue: 'Discard draft?' },
29+
description: {
30+
type: 'text',
31+
initialValue: "You can't undo this action."
32+
}
33+
},
34+
getCode
35+
};
36+
37+
export const controlledDemo = {
38+
type: 'code',
39+
code: `
40+
function ControlledAlertDialog() {
41+
const [open, setOpen] = React.useState(false);
42+
43+
return (
44+
<AlertDialog open={open} onOpenChange={setOpen}>
45+
<AlertDialog.Trigger render={<Button color="danger" />}>
46+
Delete Account
47+
</AlertDialog.Trigger>
48+
<AlertDialog.Content width={450} showCloseButton={false}>
49+
<AlertDialog.Header>
50+
<AlertDialog.Title>Delete Account</AlertDialog.Title>
51+
</AlertDialog.Header>
52+
<AlertDialog.Body>
53+
<AlertDialog.Description>
54+
Are you sure you want to delete your account? All of your data
55+
will be permanently removed. This action cannot be undone.
56+
</AlertDialog.Description>
57+
</AlertDialog.Body>
58+
<AlertDialog.Footer>
59+
<AlertDialog.Close render={<Button variant="outline" color="neutral">Cancel</Button>} />
60+
<Button color="danger" onClick={() => setOpen(false)}>Yes, delete account</Button>
61+
</AlertDialog.Footer>
62+
</AlertDialog.Content>
63+
</AlertDialog>
64+
);
65+
}`
66+
};
67+
68+
export const menuDemo = {
69+
type: 'code',
70+
code: `
71+
function MenuWithAlertDialog() {
72+
const [dialogOpen, setDialogOpen] = React.useState(false);
73+
74+
return (
75+
<React.Fragment>
76+
<Menu>
77+
<Menu.Trigger render={<Button variant="outline" />}>
78+
Actions
79+
</Menu.Trigger>
80+
<Menu.Content>
81+
<Menu.Item>Edit</Menu.Item>
82+
<Menu.Item>Duplicate</Menu.Item>
83+
<Menu.Separator />
84+
<Menu.Item onClick={() => setDialogOpen(true)}>
85+
Delete
86+
</Menu.Item>
87+
</Menu.Content>
88+
</Menu>
89+
90+
<AlertDialog open={dialogOpen} onOpenChange={setDialogOpen}>
91+
<AlertDialog.Content width={400} showCloseButton={false}>
92+
<AlertDialog.Body>
93+
<AlertDialog.Title>Delete item?</AlertDialog.Title>
94+
<AlertDialog.Description>
95+
This will permanently delete the item. You can't undo this action.
96+
</AlertDialog.Description>
97+
</AlertDialog.Body>
98+
<AlertDialog.Footer>
99+
<AlertDialog.Close render={<Button variant="outline" color="neutral">Cancel</Button>} />
100+
<Button color="danger" onClick={() => setDialogOpen(false)}>Delete</Button>
101+
</AlertDialog.Footer>
102+
</AlertDialog.Content>
103+
</AlertDialog>
104+
</React.Fragment>
105+
);
106+
}`
107+
};
108+
109+
export const discardDemo = {
110+
type: 'code',
111+
code: `
112+
<AlertDialog>
113+
<AlertDialog.Trigger render={<Button variant="outline" />}>
114+
Discard Changes
115+
</AlertDialog.Trigger>
116+
<AlertDialog.Content width={400} showCloseButton={false}>
117+
<AlertDialog.Header>
118+
<AlertDialog.Title>Unsaved Changes</AlertDialog.Title>
119+
</AlertDialog.Header>
120+
<AlertDialog.Body>
121+
<AlertDialog.Description>
122+
You have unsaved changes. Do you want to discard them or continue editing?
123+
</AlertDialog.Description>
124+
</AlertDialog.Body>
125+
<AlertDialog.Footer>
126+
<AlertDialog.Close render={<Button variant="outline" color="neutral">Continue Editing</Button>} />
127+
<AlertDialog.Close render={<Button color="danger">Discard</Button>} />
128+
</AlertDialog.Footer>
129+
</AlertDialog.Content>
130+
</AlertDialog>`
131+
};
132+
133+
export const nestedDemo = {
134+
type: 'code',
135+
code: `
136+
function NestedAlertDialogExample() {
137+
return (
138+
<AlertDialog>
139+
<AlertDialog.Trigger render={<Button color="danger" />}>
140+
Delete Workspace
141+
</AlertDialog.Trigger>
142+
<AlertDialog.Content width={450}>
143+
<AlertDialog.Header>
144+
<AlertDialog.Title>Delete Workspace</AlertDialog.Title>
145+
</AlertDialog.Header>
146+
<AlertDialog.Body>
147+
<AlertDialog.Description>
148+
This will delete the workspace and all its projects. Are you sure?
149+
</AlertDialog.Description>
150+
<AlertDialog>
151+
<AlertDialog.Trigger render={<Button color="danger" size="small" />}>
152+
Confirm Delete
153+
</AlertDialog.Trigger>
154+
<AlertDialog.Content width={400} showCloseButton={false}>
155+
<AlertDialog.Body>
156+
<AlertDialog.Title>Final Confirmation</AlertDialog.Title>
157+
<AlertDialog.Description>
158+
This is your last chance. This action is permanent and cannot be reversed.
159+
</AlertDialog.Description>
160+
</AlertDialog.Body>
161+
<AlertDialog.Footer>
162+
<AlertDialog.Close render={<Button variant="outline" color="neutral">Go Back</Button>} />
163+
<AlertDialog.Close render={<Button color="danger">Delete Everything</Button>} />
164+
</AlertDialog.Footer>
165+
</AlertDialog.Content>
166+
</AlertDialog>
167+
</AlertDialog.Body>
168+
<AlertDialog.Footer>
169+
<AlertDialog.Close render={<Button variant="outline" color="neutral">Cancel</Button>} />
170+
</AlertDialog.Footer>
171+
</AlertDialog.Content>
172+
</AlertDialog>
173+
);
174+
}`
175+
};
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
---
2+
title: AlertDialog
3+
description: A modal dialog that interrupts the user with important content and expects a response. Unlike Dialog, it does not close on outside click, requiring explicit user action.
4+
source: packages/raystack/components/alert-dialog
5+
tag: new
6+
---
7+
8+
import { playground, controlledDemo, menuDemo, discardDemo, nestedDemo } from "./demo.ts";
9+
10+
<Demo data={playground} />
11+
12+
## Anatomy
13+
14+
Import and assemble the component:
15+
16+
```tsx
17+
import { AlertDialog } from '@raystack/apsara'
18+
19+
<AlertDialog>
20+
<AlertDialog.Trigger />
21+
<AlertDialog.Content>
22+
<AlertDialog.Header>
23+
<AlertDialog.Title />
24+
</AlertDialog.Header>
25+
<AlertDialog.Body>
26+
<AlertDialog.Description />
27+
</AlertDialog.Body>
28+
<AlertDialog.Footer>
29+
<AlertDialog.Close />
30+
</AlertDialog.Footer>
31+
</AlertDialog.Content>
32+
</AlertDialog>
33+
```
34+
35+
## API Reference
36+
37+
### Root
38+
39+
Groups all parts of the alert dialog.
40+
41+
<auto-type-table path="./props.ts" name="AlertDialogProps" />
42+
43+
### Content
44+
45+
Renders the alert dialog panel overlay.
46+
47+
<auto-type-table path="./props.ts" name="AlertDialogContentProps" />
48+
49+
### Header
50+
51+
Renders the alert dialog header section.
52+
53+
<auto-type-table path="./props.ts" name="AlertDialogHeaderProps" />
54+
55+
### Title
56+
57+
Renders the alert dialog title text.
58+
59+
<auto-type-table path="./props.ts" name="AlertDialogTitleProps" />
60+
61+
### Body
62+
63+
Renders the main content area of the alert dialog.
64+
65+
<auto-type-table path="./props.ts" name="AlertDialogBodyProps" />
66+
67+
### Description
68+
69+
Renders supplementary text within the alert dialog body.
70+
71+
<auto-type-table path="./props.ts" name="AlertDialogDescriptionProps" />
72+
73+
### Trigger
74+
75+
Renders the element that opens the alert dialog.
76+
77+
<auto-type-table path="./props.ts" name="AlertDialogTriggerProps" />
78+
79+
### CloseButton
80+
81+
Renders a button that closes the alert dialog.
82+
83+
<auto-type-table path="./props.ts" name="AlertDialogCloseButtonProps" />
84+
85+
### Footer
86+
87+
Renders the alert dialog footer section.
88+
89+
<auto-type-table path="./props.ts" name="AlertDialogFooterProps" />
90+
91+
## Examples
92+
93+
### Controlled
94+
95+
Example of a controlled alert dialog with custom state management.
96+
97+
<Demo data={controlledDemo} />
98+
99+
### Composing with Menu
100+
101+
Open an alert dialog from a menu item. Since the trigger is outside the `AlertDialog.Root`, use the controlled `open` / `onOpenChange` props.
102+
103+
<Demo data={menuDemo} />
104+
105+
### Discard Changes
106+
107+
A common pattern for confirming destructive navigation. Both actions use `AlertDialog.Close` to dismiss the dialog.
108+
109+
<Demo data={discardDemo} />
110+
111+
### Nested Alert Dialogs
112+
113+
You can nest alert dialogs for multi-step confirmation flows. When a nested alert dialog opens, the parent dialog automatically scales down and becomes slightly transparent.
114+
115+
<Demo data={nestedDemo} />
116+
117+
## Accessibility
118+
119+
- Alert dialog has `role="alertdialog"` and `aria-modal="true"`
120+
- Does not close on outside click (backdrop), requiring explicit user action
121+
- Uses `aria-label` or `aria-labelledby` to identify the dialog
122+
- Uses `aria-describedby` to provide additional context
123+
- Focus is trapped within the alert dialog while open

0 commit comments

Comments
 (0)