Dialog
This library is a work-in-progress. We are releasing it early to gather feedback, but it is not ready for production.
A dialog is a modal window that overlays the main content and requires user interaction before returning to the application. Dialogs are useful for confirmations, alerts, forms, and presenting focused content.
Examples
Live Preview
Basic Dialog
This is the basic dialog content.
With Header and Footer
My Header
This dialog uses dialog header and footer components.
With Close Button
This dialog includes a close button in the top-right corner.
No Close on Escape
Try pressing the Escape key - the dialog will not close.
Use the close button instead.
No Close on Backdrop
Try clicking outside the dialog - it will not close.
Use the close button instead.
Event Handling
Try closing this dialog in different ways:
- Click the X button (triggers dialog-close)
- Press Escape (triggers dialog-cancel)
- Click the backdrop (triggers dialog-cancel)
Customized with CSS Shadow Parts
This dialog demonstrates CSS Shadow Parts customization with styled backdrop, container, header, heading, content, footer, and close button.
View Vue Code
<template>
<section>
<div class="mbe4">
<h2>Basic Dialog</h2>
</div>
<div class="stacked-mobile mbe4">
<VueButton @click="showBasicDialog">Open Basic Dialog</VueButton>
<VueDialog
v-model:open="isBasicDialogOpen"
heading="Basic Dialog"
description="This is a basic dialog with heading and description."
>
<p>This is the basic dialog content.</p>
</VueDialog>
</div>
<div class="mbe4">
<h2>With Header and Footer</h2>
</div>
<div class="stacked-mobile mbe4">
<VueButton @click="showHeaderFooterDialog">Open Dialog with Header/Footer</VueButton>
<VueDialog v-model:open="isHeaderFooterDialogOpen">
<VueDialogHeader>
<h2 class="m0 p0 b0">My Header</h2>
</VueDialogHeader>
<p>This dialog uses dialog header and footer components.</p>
<VueDialogFooter>
<div style="display: flex; gap: 0.5rem; justify-content: flex-end;">
<VueButton @click="isHeaderFooterDialogOpen = false">Cancel</VueButton>
<VueButton
variant="primary"
@click="isHeaderFooterDialogOpen = false"
>Confirm</VueButton>
</div>
</VueDialogFooter>
</VueDialog>
</div>
<div class="mbe4">
<h2>With Close Button</h2>
</div>
<div class="stacked-mobile mbe4">
<VueButton @click="showCloseButtonDialog">Open Dialog with Close Button</VueButton>
<VueDialog
v-model:open="isCloseButtonDialogOpen"
heading="Dialog with Close Button"
description="Click the X button to close this dialog."
show-close-button
>
<p>This dialog includes a close button in the top-right corner.</p>
</VueDialog>
</div>
<div class="mbe4">
<h2>No Close on Escape</h2>
</div>
<div class="stacked-mobile mbe4">
<VueButton @click="showNoEscapeDialog">Open No Escape Dialog</VueButton>
<VueDialog
v-model:open="isNoEscapeDialogOpen"
heading="No Close on Escape"
description="Pressing Escape will not close this dialog."
no-close-on-escape
show-close-button
>
<p>Try pressing the Escape key - the dialog will not close.</p>
<p>Use the close button instead.</p>
</VueDialog>
</div>
<div class="mbe4">
<h2>No Close on Backdrop</h2>
</div>
<div class="stacked-mobile mbe4">
<VueButton @click="showNoBackdropDialog">Open No Backdrop Close Dialog</VueButton>
<VueDialog
v-model:open="isNoBackdropDialogOpen"
heading="No Close on Backdrop"
description="Clicking the backdrop will not close this dialog."
no-close-on-backdrop
show-close-button
>
<p>Try clicking outside the dialog - it will not close.</p>
<p>Use the close button instead.</p>
</VueDialog>
</div>
<div class="mbe4">
<h2>Event Handling</h2>
</div>
<div class="stacked-mobile mbe4">
<VueButton @click="showEventDialog">Open Event Dialog</VueButton>
<VueDialog
v-model:open="isEventDialogOpen"
heading="Event Testing"
description="This dialog demonstrates event handling."
show-close-button
@dialog-open="handleDialogOpen"
@dialog-close="handleDialogClose"
@dialog-cancel="handleDialogCancel"
>
<p>Try closing this dialog in different ways:</p>
<ul>
<li>Click the X button (triggers dialog-close)</li>
<li>Press Escape (triggers dialog-cancel)</li>
<li>Click the backdrop (triggers dialog-cancel)</li>
</ul>
<p
v-if="lastEvent"
style="margin-top: 1rem; padding: 0.5rem; background: var(--ag-background-secondary); border-radius: 4px;"
>
Last event: <strong>{{ lastEvent }}</strong>
</p>
</VueDialog>
</div>
<div class="mbe4">
<h2>Customized with CSS Shadow Parts</h2>
</div>
<div class="stacked-mobile mbe4">
<VueButton @click="showCustomDialog">Open Customized Dialog</VueButton>
<VueDialog
v-model:open="isCustomDialogOpen"
class="custom-parts-dialog"
heading="Styled Dialog"
description="This dialog is customized using CSS Shadow Parts."
show-close-button
>
<p>This dialog demonstrates CSS Shadow Parts customization with styled backdrop, container, header, heading, content, footer, and close button.</p>
<VueDialogFooter>
<div style="display: flex; gap: 0.5rem; justify-content: flex-end;">
<VueButton @click="isCustomDialogOpen = false">Close</VueButton>
</div>
</VueDialogFooter>
</VueDialog>
</div>
</section>
</template>
<script>
import VueDialog, {
VueDialogHeader,
VueDialogFooter,
} from "agnosticui-core/dialog/vue";
import VueButton from "agnosticui-core/button/vue";
export default {
name: "DialogExamples",
components: {
VueDialog,
VueDialogHeader,
VueDialogFooter,
VueButton,
},
data() {
return {
isBasicDialogOpen: false,
isHeaderFooterDialogOpen: false,
isCloseButtonDialogOpen: false,
isNoEscapeDialogOpen: false,
isNoBackdropDialogOpen: false,
isEventDialogOpen: false,
isCustomDialogOpen: false,
lastEvent: null,
};
},
methods: {
showBasicDialog() {
this.isBasicDialogOpen = true;
},
showHeaderFooterDialog() {
this.isHeaderFooterDialogOpen = true;
},
showCloseButtonDialog() {
this.isCloseButtonDialogOpen = true;
},
showNoEscapeDialog() {
this.isNoEscapeDialogOpen = true;
},
showNoBackdropDialog() {
this.isNoBackdropDialogOpen = true;
},
showEventDialog() {
this.isEventDialogOpen = true;
this.lastEvent = null;
},
showCustomDialog() {
this.isCustomDialogOpen = true;
},
handleDialogOpen() {
this.lastEvent = "dialog-open";
},
handleDialogClose() {
this.lastEvent = "dialog-close";
},
handleDialogCancel() {
this.lastEvent = "dialog-cancel";
},
},
};
</script>
<style scoped>
.custom-parts-dialog::part(ag-dialog-backdrop) {
background: linear-gradient(
135deg,
rgba(102, 126, 234, 0.8) 0%,
rgba(118, 75, 162, 0.8) 100%
);
}
.custom-parts-dialog::part(ag-dialog-container) {
border: 3px solid #667eea;
box-shadow: 0 20px 60px rgba(102, 126, 234, 0.4);
}
.custom-parts-dialog::part(ag-dialog-header) {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 1.5rem;
margin: -1.5rem -1.5rem 1rem -1.5rem;
border-radius: 0.5rem 0.5rem 0 0;
}
.custom-parts-dialog::part(ag-dialog-heading) {
font-size: 1.5rem;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.custom-parts-dialog::part(ag-dialog-content) {
padding: 0 0.5rem;
}
.custom-parts-dialog::part(ag-dialog-footer) {
padding: 1rem;
margin: 1rem -1.5rem -1.5rem -1.5rem;
border-radius: 0 0 0.5rem 0.5rem;
}
.custom-parts-dialog::part(ag-dialog-close-button) {
background: rgba(255, 255, 255, 0.2);
color: white;
border-radius: 50%;
width: 2rem;
height: 2rem;
font-size: 1.5rem;
}
.custom-parts-dialog::part(ag-dialog-close-button):hover {
background: rgba(255, 255, 255, 0.3);
}
</style>
Live Preview
View Lit / Web Component Code
import { LitElement, html, css } from 'lit';
import 'agnosticui-core/dialog';
import 'agnosticui-core/button';
export class DialogLitExamples extends LitElement {
static styles = css`
.custom-parts-dialog::part(ag-dialog-backdrop) {
background: linear-gradient(
135deg,
rgba(102, 126, 234, 0.8) 0%,
rgba(118, 75, 162, 0.8) 100%
);
}
.custom-parts-dialog::part(ag-dialog-container) {
border: 3px solid #667eea;
box-shadow: 0 20px 60px rgba(102, 126, 234, 0.4);
}
.custom-parts-dialog::part(ag-dialog-header) {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 1.5rem;
margin: -1.5rem -1.5rem 1rem -1.5rem;
border-radius: 0.5rem 0.5rem 0 0;
}
.custom-parts-dialog::part(ag-dialog-heading) {
font-size: 1.5rem;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.custom-parts-dialog::part(ag-dialog-content) {
padding: 0 0.5rem;
}
.custom-parts-dialog::part(ag-dialog-footer) {
padding: 1rem;
margin: 1rem -1.5rem -1.5rem -1.5rem;
border-radius: 0 0 0.5rem 0.5rem;
}
.custom-parts-dialog::part(ag-dialog-close-button) {
background: rgba(255, 255, 255, 0.2);
color: white;
border-radius: 50%;
width: 2rem;
height: 2rem;
font-size: 1.5rem;
}
.custom-parts-dialog::part(ag-dialog-close-button):hover {
background: rgba(255, 255, 255, 0.3);
}
`;
// Render in light DOM to access global utility classes
createRenderRoot() {
return this;
}
constructor() {
super();
this.isBasicDialogOpen = false;
this.isHeaderFooterDialogOpen = false;
this.isCloseButtonDialogOpen = false;
this.isNoEscapeDialogOpen = false;
this.isNoBackdropDialogOpen = false;
this.isEventDialogOpen = false;
this.isCustomDialogOpen = false;
this.lastEvent = null;
}
firstUpdated() {
// Set up event listeners for dialogs
const eventDialog = this.querySelector('#event-dialog');
if (eventDialog) {
eventDialog.addEventListener('dialog-open', () => {
this.lastEvent = 'dialog-open';
this.requestUpdate();
});
eventDialog.addEventListener('dialog-close', () => {
this.lastEvent = 'dialog-close';
this.requestUpdate();
});
eventDialog.addEventListener('dialog-cancel', () => {
this.lastEvent = 'dialog-cancel';
this.requestUpdate();
});
}
// Add close event listeners to sync state
this.querySelectorAll('ag-dialog').forEach((dialog) => {
dialog.addEventListener('dialog-close', (e) => {
const dialogId = e.target.id;
this.handleDialogClose(dialogId);
});
dialog.addEventListener('dialog-cancel', (e) => {
const dialogId = e.target.id;
this.handleDialogClose(dialogId);
});
});
}
handleDialogClose(dialogId) {
switch (dialogId) {
case 'basic-dialog':
this.isBasicDialogOpen = false;
break;
case 'header-footer-dialog':
this.isHeaderFooterDialogOpen = false;
break;
case 'close-button-dialog':
this.isCloseButtonDialogOpen = false;
break;
case 'no-escape-dialog':
this.isNoEscapeDialogOpen = false;
break;
case 'no-backdrop-dialog':
this.isNoBackdropDialogOpen = false;
break;
case 'event-dialog':
this.isEventDialogOpen = false;
break;
case 'custom-dialog':
this.isCustomDialogOpen = false;
break;
}
this.requestUpdate();
}
showBasicDialog() {
this.isBasicDialogOpen = true;
this.requestUpdate();
setTimeout(() => {
const dialog = this.querySelector('#basic-dialog');
if (dialog) dialog.open = true;
}, 0);
}
showHeaderFooterDialog() {
this.isHeaderFooterDialogOpen = true;
this.requestUpdate();
setTimeout(() => {
const dialog = this.querySelector('#header-footer-dialog');
if (dialog) dialog.open = true;
}, 0);
}
showCloseButtonDialog() {
this.isCloseButtonDialogOpen = true;
this.requestUpdate();
setTimeout(() => {
const dialog = this.querySelector('#close-button-dialog');
if (dialog) dialog.open = true;
}, 0);
}
showNoEscapeDialog() {
this.isNoEscapeDialogOpen = true;
this.requestUpdate();
setTimeout(() => {
const dialog = this.querySelector('#no-escape-dialog');
if (dialog) dialog.open = true;
}, 0);
}
showNoBackdropDialog() {
this.isNoBackdropDialogOpen = true;
this.requestUpdate();
setTimeout(() => {
const dialog = this.querySelector('#no-backdrop-dialog');
if (dialog) dialog.open = true;
}, 0);
}
showEventDialog() {
this.isEventDialogOpen = true;
this.lastEvent = null;
this.requestUpdate();
setTimeout(() => {
const dialog = this.querySelector('#event-dialog');
if (dialog) dialog.open = true;
}, 0);
}
showCustomDialog() {
this.isCustomDialogOpen = true;
this.requestUpdate();
setTimeout(() => {
const dialog = this.querySelector('#custom-dialog');
if (dialog) dialog.open = true;
}, 0);
}
render() {
return html`
<style>
.custom-parts-dialog::part(ag-dialog-backdrop) {
background: linear-gradient(
135deg,
rgba(102, 126, 234, 0.8) 0%,
rgba(118, 75, 162, 0.8) 100%
);
}
.custom-parts-dialog::part(ag-dialog-container) {
border: 3px solid #667eea;
box-shadow: 0 20px 60px rgba(102, 126, 234, 0.4);
}
.custom-parts-dialog::part(ag-dialog-header) {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 1.5rem;
margin: -1.5rem -1.5rem 1rem -1.5rem;
border-radius: 0.5rem 0.5rem 0 0;
}
.custom-parts-dialog::part(ag-dialog-heading) {
font-size: 1.5rem;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.custom-parts-dialog::part(ag-dialog-content) {
padding: 0 0.5rem;
}
.custom-parts-dialog::part(ag-dialog-footer) {
padding: 1rem;
margin: 1rem -1.5rem -1.5rem -1.5rem;
border-radius: 0 0 0.5rem 0.5rem;
}
.custom-parts-dialog::part(ag-dialog-close-button) {
background: rgba(255, 255, 255, 0.2);
color: white;
border-radius: 50%;
width: 2rem;
height: 2rem;
font-size: 1.5rem;
}
.custom-parts-dialog::part(ag-dialog-close-button):hover {
background: rgba(255, 255, 255, 0.3);
}
</style>
<section>
<!-- Basic Dialog -->
<div class="mbe4">
<h2>Basic Dialog</h2>
</div>
<div class="stacked-mobile mbe4">
<ag-button @click=${this.showBasicDialog}>Open Basic Dialog</ag-button>
<ag-dialog
id="basic-dialog"
heading="Basic Dialog"
description="This is a basic dialog with heading and description."
>
<p>This is the basic dialog content.</p>
</ag-dialog>
</div>
<!-- With Header and Footer -->
<div class="mbe4">
<h2>With Header and Footer</h2>
</div>
<div class="stacked-mobile mbe4">
<ag-button @click=${this.showHeaderFooterDialog}>Open Dialog with Header/Footer</ag-button>
<ag-dialog id="header-footer-dialog">
<ag-dialog-header>
<h2 class="m0 p0 b0">My Header</h2>
</ag-dialog-header>
<p>This dialog uses dialog header and footer components.</p>
<ag-dialog-footer>
<div style="display: flex; gap: 0.5rem; justify-content: flex-end;">
<ag-button @click=${() => {
const dialog = this.querySelector('#header-footer-dialog');
if (dialog) dialog.open = false;
}}>Cancel</ag-button>
<ag-button
variant="primary"
@click=${() => {
const dialog = this.querySelector('#header-footer-dialog');
if (dialog) dialog.open = false;
}}
>Confirm</ag-button>
</div>
</ag-dialog-footer>
</ag-dialog>
</div>
<!-- With Close Button -->
<div class="mbe4">
<h2>With Close Button</h2>
</div>
<div class="stacked-mobile mbe4">
<ag-button @click=${this.showCloseButtonDialog}>Open Dialog with Close Button</ag-button>
<ag-dialog
id="close-button-dialog"
heading="Dialog with Close Button"
description="Click the X button to close this dialog."
show-close-button
>
<p>This dialog includes a close button in the top-right corner.</p>
</ag-dialog>
</div>
<!-- No Close on Escape -->
<div class="mbe4">
<h2>No Close on Escape</h2>
</div>
<div class="stacked-mobile mbe4">
<ag-button @click=${this.showNoEscapeDialog}>Open No Escape Dialog</ag-button>
<ag-dialog
id="no-escape-dialog"
heading="No Close on Escape"
description="Pressing Escape will not close this dialog."
no-close-on-escape
show-close-button
>
<p>Try pressing the Escape key - the dialog will not close.</p>
<p>Use the close button instead.</p>
</ag-dialog>
</div>
<!-- No Close on Backdrop -->
<div class="mbe4">
<h2>No Close on Backdrop</h2>
</div>
<div class="stacked-mobile mbe4">
<ag-button @click=${this.showNoBackdropDialog}>Open No Backdrop Close Dialog</ag-button>
<ag-dialog
id="no-backdrop-dialog"
heading="No Close on Backdrop"
description="Clicking the backdrop will not close this dialog."
no-close-on-backdrop
show-close-button
>
<p>Try clicking outside the dialog - it will not close.</p>
<p>Use the close button instead.</p>
</ag-dialog>
</div>
<!-- Event Handling -->
<div class="mbe4">
<h2>Event Handling</h2>
</div>
<div class="stacked-mobile mbe4">
<ag-button @click=${this.showEventDialog}>Open Event Dialog</ag-button>
<ag-dialog
id="event-dialog"
heading="Event Testing"
description="This dialog demonstrates event handling."
show-close-button
>
<p>Try closing this dialog in different ways:</p>
<ul>
<li>Click the X button (triggers dialog-close)</li>
<li>Press Escape (triggers dialog-cancel)</li>
<li>Click the backdrop (triggers dialog-cancel)</li>
</ul>
${this.lastEvent ? html`
<p style="margin-top: 1rem; padding: 0.5rem; background: var(--ag-background-secondary); border-radius: 4px;">
Last event: <strong>${this.lastEvent}</strong>
</p>
` : ''}
</ag-dialog>
</div>
<!-- Customized with CSS Shadow Parts -->
<div class="mbe4">
<h2>Customized with CSS Shadow Parts</h2>
</div>
<div class="stacked-mobile mbe4">
<ag-button @click=${this.showCustomDialog}>Open Customized Dialog</ag-button>
<ag-dialog
id="custom-dialog"
class="custom-parts-dialog"
heading="Styled Dialog"
description="This dialog is customized using CSS Shadow Parts."
show-close-button
>
<p>This dialog demonstrates CSS Shadow Parts customization with styled backdrop, container, header, heading, content, footer, and close button.</p>
<ag-dialog-footer>
<div style="display: flex; gap: 0.5rem; justify-content: flex-end;">
<ag-button @click=${() => {
const dialog = this.querySelector('#custom-dialog');
if (dialog) dialog.open = false;
}}>Close</ag-button>
</div>
</ag-dialog-footer>
</ag-dialog>
</div>
</section>
`;
}
}
// Register the custom element
customElements.define('dialog-lit-examples', DialogLitExamples);
Interactive Preview: Click the "Open in StackBlitz" button below to see this example running live in an interactive playground.
View React Code
import { useState } from "react";
import ReactDialog, {
ReactDialogHeader,
ReactDialogFooter,
} from "agnosticui-core/dialog/react";
import { ReactButton } from "agnosticui-core/button/react";
export default function DialogReactExamples() {
const [isBasicDialogOpen, setIsBasicDialogOpen] = useState(false);
const [isHeaderFooterDialogOpen, setIsHeaderFooterDialogOpen] = useState(false);
const [isCloseButtonDialogOpen, setIsCloseButtonDialogOpen] = useState(false);
const [isNoEscapeDialogOpen, setIsNoEscapeDialogOpen] = useState(false);
const [isNoBackdropDialogOpen, setIsNoBackdropDialogOpen] = useState(false);
const [isEventDialogOpen, setIsEventDialogOpen] = useState(false);
const [isCustomDialogOpen, setIsCustomDialogOpen] = useState(false);
const [lastEvent, setLastEvent] = useState(null);
const handleDialogOpen = () => {
setLastEvent("dialog-open");
};
const handleDialogClose = () => {
setLastEvent("dialog-close");
};
const handleDialogCancel = () => {
setLastEvent("dialog-cancel");
};
const showEventDialog = () => {
setIsEventDialogOpen(true);
setLastEvent(null);
};
return (
<section>
{/* Basic Dialog */}
<div className="mbe4">
<h2>Basic Dialog</h2>
</div>
<div className="stacked-mobile mbe4">
<ReactButton onClick={() => setIsBasicDialogOpen(true)}>
Open Basic Dialog
</ReactButton>
<ReactDialog
open={isBasicDialogOpen}
onOpenChange={setIsBasicDialogOpen}
heading="Basic Dialog"
description="This is a basic dialog with heading and description."
>
<p>This is the basic dialog content.</p>
</ReactDialog>
</div>
{/* With Header and Footer */}
<div className="mbe4">
<h2>With Header and Footer</h2>
</div>
<div className="stacked-mobile mbe4">
<ReactButton onClick={() => setIsHeaderFooterDialogOpen(true)}>
Open Dialog with Header/Footer
</ReactButton>
<ReactDialog
open={isHeaderFooterDialogOpen}
onOpenChange={setIsHeaderFooterDialogOpen}
>
<ReactDialogHeader>
<h2 className="m0 p0 b0">My Header</h2>
</ReactDialogHeader>
<p>This dialog uses dialog header and footer components.</p>
<ReactDialogFooter>
<div style={{ display: "flex", gap: "0.5rem", justifyContent: "flex-end" }}>
<ReactButton onClick={() => setIsHeaderFooterDialogOpen(false)}>
Cancel
</ReactButton>
<ReactButton
variant="primary"
onClick={() => setIsHeaderFooterDialogOpen(false)}
>
Confirm
</ReactButton>
</div>
</ReactDialogFooter>
</ReactDialog>
</div>
{/* With Close Button */}
<div className="mbe4">
<h2>With Close Button</h2>
</div>
<div className="stacked-mobile mbe4">
<ReactButton onClick={() => setIsCloseButtonDialogOpen(true)}>
Open Dialog with Close Button
</ReactButton>
<ReactDialog
open={isCloseButtonDialogOpen}
onOpenChange={setIsCloseButtonDialogOpen}
heading="Dialog with Close Button"
description="Click the X button to close this dialog."
showCloseButton
>
<p>This dialog includes a close button in the top-right corner.</p>
</ReactDialog>
</div>
{/* No Close on Escape */}
<div className="mbe4">
<h2>No Close on Escape</h2>
</div>
<div className="stacked-mobile mbe4">
<ReactButton onClick={() => setIsNoEscapeDialogOpen(true)}>
Open No Escape Dialog
</ReactButton>
<ReactDialog
open={isNoEscapeDialogOpen}
onOpenChange={setIsNoEscapeDialogOpen}
heading="No Close on Escape"
description="Pressing Escape will not close this dialog."
noCloseOnEscape
showCloseButton
>
<p>Try pressing the Escape key - the dialog will not close.</p>
<p>Use the close button instead.</p>
</ReactDialog>
</div>
{/* No Close on Backdrop */}
<div className="mbe4">
<h2>No Close on Backdrop</h2>
</div>
<div className="stacked-mobile mbe4">
<ReactButton onClick={() => setIsNoBackdropDialogOpen(true)}>
Open No Backdrop Close Dialog
</ReactButton>
<ReactDialog
open={isNoBackdropDialogOpen}
onOpenChange={setIsNoBackdropDialogOpen}
heading="No Close on Backdrop"
description="Clicking the backdrop will not close this dialog."
noCloseOnBackdrop
showCloseButton
>
<p>Try clicking outside the dialog - it will not close.</p>
<p>Use the close button instead.</p>
</ReactDialog>
</div>
{/* Event Handling */}
<div className="mbe4">
<h2>Event Handling</h2>
</div>
<div className="stacked-mobile mbe4">
<ReactButton onClick={showEventDialog}>Open Event Dialog</ReactButton>
<ReactDialog
open={isEventDialogOpen}
onOpenChange={setIsEventDialogOpen}
heading="Event Testing"
description="This dialog demonstrates event handling."
showCloseButton
onDialogOpen={handleDialogOpen}
onDialogClose={handleDialogClose}
onDialogCancel={handleDialogCancel}
>
<p>Try closing this dialog in different ways:</p>
<ul>
<li>Click the X button (triggers dialog-close)</li>
<li>Press Escape (triggers dialog-cancel)</li>
<li>Click the backdrop (triggers dialog-cancel)</li>
</ul>
{lastEvent && (
<p
style={{
marginTop: "1rem",
padding: "0.5rem",
background: "var(--ag-background-secondary)",
borderRadius: "4px",
}}
>
Last event: <strong>{lastEvent}</strong>
</p>
)}
</ReactDialog>
</div>
{/* Customized with CSS Shadow Parts */}
<div className="mbe4">
<h2>Customized with CSS Shadow Parts</h2>
</div>
<div className="stacked-mobile mbe4">
<ReactButton onClick={() => setIsCustomDialogOpen(true)}>
Open Customized Dialog
</ReactButton>
<ReactDialog
open={isCustomDialogOpen}
onOpenChange={setIsCustomDialogOpen}
className="custom-parts-dialog"
heading="Styled Dialog"
description="This dialog is customized using CSS Shadow Parts."
showCloseButton
>
<p>
This dialog demonstrates CSS Shadow Parts customization with styled
backdrop, container, header, heading, content, footer, and close button.
</p>
<ReactDialogFooter>
<div style={{ display: "flex", gap: "0.5rem", justifyContent: "flex-end" }}>
<ReactButton onClick={() => setIsCustomDialogOpen(false)}>
Close
</ReactButton>
</div>
</ReactDialogFooter>
</ReactDialog>
</div>
</section>
);
}
Usage
TIP
The framework examples below import AgnosticUI as an npm package. Alternatively, you can use the CLI for complete control, AI/LLM visibility, and full code ownership:
npx ag init --framework FRAMEWORK # react, vue, lit, svelte, etc.
npx ag add DialogThe CLI copies source code directly into your project, giving you full visibility and control. After running npx ag add, you'll receive exact import instructions.
Vue
<template>
<section>
<VueButton @click="showDialog">Open Dialog</VueButton>
<VueDialog
v-model:open="isOpen"
heading="Dialog Title"
description="This is a dialog description"
@dialog-close="handleClose"
>
<p>This is the dialog content.</p>
</VueDialog>
<VueButton @click="showCustomDialog">Open Custom Dialog</VueButton>
<VueDialog v-model:open="isCustomOpen">
<VueDialogHeader>
<h2 style="margin: 0;">Custom Header</h2>
</VueDialogHeader>
<p>Dialog with custom header and footer.</p>
<VueDialogFooter>
<div style="display: flex; gap: 0.5rem; justify-content: flex-end;">
<VueButton @click="isCustomOpen = false">Cancel</VueButton>
<VueButton variant="primary" @click="isCustomOpen = false"
>Confirm</VueButton
>
</div>
</VueDialogFooter>
</VueDialog>
<VueButton @click="showCloseButtonDialog">Open Dialog</VueButton>
<VueDialog
v-model:open="isCloseButtonOpen"
heading="Dialog with Close Button"
show-close-button
>
<p>This dialog includes a close button.</p>
</VueDialog>
<VueButton @click="showEventDialog">Open Dialog</VueButton>
<VueDialog
v-model:open="isEventOpen"
heading="Event Testing"
show-close-button
@dialog-open="handleOpen"
@dialog-close="handleClose"
@dialog-cancel="handleCancel"
>
<p>Try closing this dialog different ways to see events.</p>
</VueDialog>
</section>
</template>
<script>
import VueDialog, {
VueDialogHeader,
VueDialogFooter,
} from "agnosticui-core/dialog/vue";
import VueButton from "agnosticui-core/button/vue";
export default {
components: {
VueDialog,
VueDialogHeader,
VueDialogFooter,
VueButton,
},
data() {
return {
isOpen: false,
isCustomOpen: false,
isCloseButtonOpen: false,
isEventOpen: false,
};
},
methods: {
showDialog() {
this.isOpen = true;
},
showCustomDialog() {
this.isCustomOpen = true;
},
showCloseButtonDialog() {
this.isCloseButtonOpen = true;
},
showEventDialog() {
this.isEventOpen = true;
},
handleOpen() {
console.log("Dialog opened");
},
handleClose() {
console.log("Dialog closed");
},
handleCancel() {
console.log("Dialog cancelled");
},
},
};
</script>React
import { useState } from "react";
import {
ReactDialog,
DialogHeader,
DialogFooter,
} from "agnosticui-core/dialog/react";
export default function DialogExample() {
const [isOpen, setIsOpen] = useState(false);
const [isCustomOpen, setIsCustomOpen] = useState(false);
const [isCloseButtonOpen, setIsCloseButtonOpen] = useState(false);
const handleClose = () => {
console.log("Dialog closed");
setIsOpen(false);
};
const handleCancel = () => {
console.log("Dialog cancelled");
setIsOpen(false);
};
return (
<section>
<button onClick={() => setIsOpen(true)}>Open Dialog</button>
<ReactDialog
open={isOpen}
heading="Dialog Title"
description="This is a dialog description"
onDialogClose={handleClose}
onDialogCancel={handleCancel}
>
<p>This is the dialog content.</p>
</ReactDialog>
<button onClick={() => setIsCustomOpen(true)}>Open Custom Dialog</button>
<ReactDialog
open={isCustomOpen}
onDialogClose={() => setIsCustomOpen(false)}
onDialogCancel={() => setIsCustomOpen(false)}
>
<DialogHeader>
<h2 style={{ margin: 0 }}>Custom Header</h2>
</DialogHeader>
<p>Dialog with custom header and footer.</p>
<DialogFooter>
<div
style={{
display: "flex",
gap: "0.5rem",
justifyContent: "flex-end",
}}
>
<button onClick={() => setIsCustomOpen(false)}>Cancel</button>
<button onClick={() => setIsCustomOpen(false)}>Confirm</button>
</div>
</DialogFooter>
</ReactDialog>
<button onClick={() => setIsCloseButtonOpen(true)}>Open Dialog</button>
<ReactDialog
open={isCloseButtonOpen}
heading="Dialog with Close Button"
showCloseButton={true}
onDialogClose={() => setIsCloseButtonOpen(false)}
onDialogCancel={() => setIsCloseButtonOpen(false)}
>
<p>This dialog includes a close button.</p>
</ReactDialog>
</section>
);
}Lit (Web Components)
<script type="module">
import 'agnosticui-core/dialog';
@customElement('my-element')
export class MyElement extends LitElement {
firstUpdated() {
const dialog = this.shadowRoot?.querySelector('#my-dialog') as any;
const openButton = this.shadowRoot?.querySelector('#open-dialog');
openButton?.addEventListener('click', () => {
if (dialog) {
dialog.open = true;
}
});
dialog?.addEventListener('dialog-close', () => {
dialog.open = false;
console.log('Dialog closed');
});
dialog?.addEventListener('dialog-cancel', () => {
dialog.open = false;
console.log('Dialog cancelled');
});
}
// ... rest of your class ...
}
</script>
<section>
<button id="open-dialog">Open Dialog</button>
<ag-dialog
id="my-dialog"
heading="Dialog Title"
description="This is a dialog description"
>
<p>This is the dialog content.</p>
</ag-dialog>
<ag-dialog id="custom-dialog">
<div slot="header">
<h2 style="margin: 0;">Custom Header</h2>
</div>
<p>Dialog with custom header and footer.</p>
<div
slot="footer"
style="display: flex; gap: 0.5rem; justify-content: flex-end;"
>
<button>Cancel</button>
<button>Confirm</button>
</div>
</ag-dialog>
<ag-dialog heading="Dialog with Close Button" show-close-button>
<p>This dialog includes a close button.</p>
</ag-dialog>
</section>Props
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | false | Whether the dialog is open |
heading | string | '' | The heading text for the dialog |
description | string | '' | The description text for the dialog |
noCloseOnEscape | boolean | false | Prevents closing the dialog when pressing the Escape key |
noCloseOnBackdrop | boolean | false | Prevents closing the dialog when clicking the backdrop |
showCloseButton | boolean | false | Shows a close button (×) in the top-right corner of the dialog |
Events
| Event | Framework | Detail | Description |
|---|---|---|---|
dialog-open | Vue: @dialog-openReact: onDialogOpenLit: @dialog-open | void | Fired when the dialog opens. |
dialog-close | Vue: @dialog-closeReact: onDialogCloseLit: @dialog-close | void | Fired when the dialog closes via the close button. |
dialog-cancel | Vue: @dialog-cancelReact: onDialogCancelLit: @dialog-cancel | void | Fired when the dialog is cancelled (Escape key or backdrop click). |
Event Handling Examples
Vue:
<VueDialog
v-model:open="isOpen"
@dialog-open="handleOpen"
@dialog-close="handleClose"
@dialog-cancel="handleCancel"
>
<p>Dialog content</p>
</VueDialog>React:
<ReactDialog
open={isOpen}
onDialogOpen={(e) => console.log("Dialog opened", e)}
onDialogClose={(e) => setIsOpen(false)}
onDialogCancel={(e) => setIsOpen(false)}
>
<p>Dialog content</p>
</ReactDialog>Lit:
<script>
const dialog = document.querySelector("ag-dialog");
dialog.addEventListener("dialog-open", (e) => {
console.log("Dialog opened", e);
});
dialog.addEventListener("dialog-close", (e) => {
console.log("Dialog closed", e);
});
</script>
<ag-dialog id="my-dialog"></ag-dialog>
<script>
const dialog = document.querySelector("#my-dialog");
dialog.onDialogOpen = (e) => console.log("Dialog opened", e);
dialog.onDialogClose = (e) => console.log("Dialog closed", e);
</script>Slots
Vue
- Default slot: Main content of the dialog
- VueDialogHeader: Custom header content (replaces
headingprop when used) - VueDialogFooter: Footer content for action buttons
React
- children: Main content of the dialog
- DialogHeader: Custom header content (replaces
headingprop when used) - DialogFooter: Footer content for action buttons
Lit
- Default slot: Main content of the dialog
- slot="header": Custom header content (replaces
headingprop when used) - slot="footer": Footer content for action buttons
CSS Shadow Parts
Shadow Parts allow you to style internal elements of the dialog from outside the shadow DOM using the ::part() CSS selector.
| Part | Description |
|---|---|
ag-dialog-backdrop | The backdrop overlay element behind the dialog |
ag-dialog-container | The main dialog container that holds all dialog content |
ag-dialog-header | The header section wrapper |
ag-dialog-heading | The heading text element (when using heading prop) |
ag-dialog-close-button | The close button (when showCloseButton is true) |
ag-dialog-content | The main content section wrapper |
ag-dialog-footer | The footer section wrapper |
Customization Example
ag-dialog::part(ag-dialog-backdrop) {
background: linear-gradient(
135deg,
rgba(102, 126, 234, 0.8) 0%,
rgba(118, 75, 162, 0.8) 100%
);
}
ag-dialog::part(ag-dialog-container) {
background: linear-gradient(to bottom, #ffffff, #f0f4ff);
border: 3px solid #667eea;
box-shadow: 0 20px 60px rgba(102, 126, 234, 0.4);
}
ag-dialog::part(ag-dialog-header) {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 1.5rem;
}
ag-dialog::part(ag-dialog-heading) {
font-size: 1.5rem;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
ag-dialog::part(ag-dialog-close-button) {
background: rgba(255, 255, 255, 0.2);
color: white;
border-radius: 50%;
}
ag-dialog::part(ag-dialog-content) {
padding: 0 0.5rem;
}
ag-dialog::part(ag-dialog-footer) {
background: #f0f4ff;
padding: 1rem;
}Accessibility
The Dialog implements the WAI-ARIA Dialog (Modal) Pattern:
- Uses
role="dialog"andaria-modal="true"for proper screen reader announcement - Implements focus trap to keep keyboard focus within the dialog
- Pressing Escape closes the dialog (unless
noCloseOnEscapeis true) - Clicking the backdrop closes the dialog (unless
noCloseOnBackdropis true) - Returns focus to the triggering element when closed
- Prevents background scroll when dialog is open
- Close button has
aria-label="Close dialog"for screen readers - Dialog can be labeled via
headingprop or custom header with proper heading element - Keyboard navigation cycles through all focusable elements within the dialog
- Supports Tab and Shift+Tab for navigation within the focus trap
Best Practices
- Always provide a heading (via
headingprop or custom header) for accessibility - Use
showCloseButtonor provide explicit close actions in footer - For critical actions, consider setting
noCloseOnBackdropandnoCloseOnEscapeto prevent accidental dismissal - Ensure focus is managed properly by using semantic buttons for triggers
- Keep dialog content concise and focused on a single task
v-model Support (Vue)
The Vue Dialog component supports v-model:open for two-way binding:
<VueDialog v-model:open="isDialogOpen">
<p>Dialog content</p>
</VueDialog>This automatically syncs the dialog's open state with your component's data.