Dialog
AgnosticUI's Dialog component wraps Kitty Giraudel's a11y-dialog — an accessible and inclusive dialog component that has been battle tested over several versions to date.
Examples
Dialog
The following button opens the dialog because we've obtained a dialog instance
handle via the @instance="assignDialogRef"
event hook. This is Vue syntax but the other framework implementations emit the instance
event similarly:
The following also opens because a11y-dialog will bind to element with data-a11y-dialog-show
attribute:
Usage
Note that by default Dialog
will use role="dialog"
and behave like a dialog. This means that in addition to the open/close trigger button dismissing the dialog, you can also click ESC
or click outside the dialog e.g. on the overlay and the dialog will be dismissed.
If you'd like a sticky dialog that only closes by toggling the trigger button and/or a close button you've setup, you can pass role="alertdialog"
and ESC
or click outside the dialog will NOT dismiss the dialog. Read more about this on a11y-dialog's alertdialog docs page.
React
View source
import { useRef } from 'react';
import "agnostic-react/dist/common.min.css";
import "agnostic-react/dist/esm/index.css";
import { Dialog } from "agnostic-react";
export const YourComponent = () => {
const dialog = useRef();
const dialog2 = useRef();
const dialogPropsDefault = {
id: 'ag-dialog-test',
title: 'Dialog Test',
classNames: {
// Note we don't have to pass in ALL classNames props and those
// not included will fallback to react-a11y-dialog's defaults
// See https://github.com/KittyGiraudel/react-a11y-dialog#api
title: 'h3 mbe18 h4 mbe18 flex justify-center',
}
};
return {
<>
<div className="mbs16 mbe24">
<Button onClick={() => dialog.current.show()} type="button" mode="primary" isBordered isRounded isBlock>Open the dialog</Button>
<Dialog
{...dialogPropsDefault}
dialogRef={dialogInstance => (dialog.current = dialogInstance)}
isAnimationSlideUp={true}
>
<p className="mbs16 mbe16" id="dialog-example-description">
Fill in the form below to receive our newsletter!
</p>
<form className="dialog-form-demo">
<Input isRounded label="Email (required)" type="email" name="EMAIL" id="email" placeholder="email@example.com" required />
<div className="mbe16" />
<Button type="submit" mode="primary" isRounded isBlock>Sign Up</Button>
</form>
</Dialog>
</div>
<div className="mbs16 mbe24">
<Button onClick={() => dialog2.current.show()} type="button" mode="primary" isBordered isRounded isBlock>Open dialog 2</Button>
<Dialog
{...dialogPropsDefault}
id="dialog-2"
title="Dialog — Custom Close Button"
dialogRef={instance => (dialog2.current = instance)}
classNames={{ title: 'h4 mbe18', closeButton: 'close-button-demo' }}
isAnimationFadeIn
isAnimationSlideUp
closeButtonPosition="last"
closeButtonContent={<Button type="faux" isRounded isBordered isBlock>Cancel</Button>}
>
<p className="mbs16 mbe16" id="dialog-example-description">
The <code>close-button-demo</code> class is in App.css (for the Cancel button at bottom). Otherwise, we use an AgnosticUI button of <code>type="faux</code> which generates a div that looks like a button. As <code>react-a11y-dialog</code> generates its own button
around <code>closeButtonContent</code>, this prevents an unwanted nested buttons situation.
</p>
<p className="mbe16">You'll also notice that this dialog did not <span class="quoted">slide up</span> as we have not passed in true to <code>isAnimationSlideUp</code> and this animation defaults to false. The other animation is <code>isAnimationFadeIn</code> which defaults to true. You can set it <code>false</code> if you wish to remove it.
</p>
<form className="dialog-form-demo">
<Input isRounded label="Email (required)" type="email" name="EMAIL" id="email" placeholder="email@example.com" required />
<div className="mbe16" />
<Button type="submit" mode="primary" isRounded isBlock>Sign Up</Button>
</form>
</Dialog>
</div>
</>
}
}
React: component source
Vue 3
View source
<template>
<section class="mbe40">
<div class="h4 mbe32 flex items-center">
<img
width="24"
height="24"
src="/assets/Vue-icon.svg"
alt="Vue logo"
class="mie12"
/>Vue 3 Dialog
</div>
<div class="mbs16 mbe24">
<Button
mode="primary"
:is-bordered="true"
:is-block="true"
:is-rounded="true"
type="button"
@click="openDialog"
>
Open the dialog
</Button>
<Dialog
id="vue-dialog"
dialog-root="#portal-root"
:is-animation-fade-in="true"
:is-animation-slide-up="true"
role="dialog"
:class-names="{
title: 'h4 mbe18 flex justify-center',
}"
@instance="assignDialogRef"
>
<template #title>
My Dialog 1
</template>
<p
class="mbs16 mbe16"
id="dialog-example-description"
>
Fill in the form below to receive our newsletter!
</p>
<form class="dialog-form-demo">
<Input
:is-rounded="true"
label="Email (required)"
type="email"
name="EMAIL"
id="email"
placeholder="email@example.com"
required
/>
<div class="mbe16" />
<Button
type="submit"
mode="primary"
:is-rounded="true"
:is-block="true"
>
Sign Up
</Button>
</form>
</Dialog>
</div>
<div class="mbs16 mbe24">
<Button
mode="primary"
:is-bordered="true"
:is-block="true"
:is-rounded="true"
type="button"
@click="openDialog2"
>
Open dialog 2
</Button>
<Dialog
id="2vue2dialog"
dialog-root="#portal-root"
close-button-label="My close button label"
close-button-position="last"
role="alertdialog"
:class-names="{
container: styles['my-dialog-container'],
overlay: styles['my-dialog-overlay'],
document: styles['my-dialog-content'],
title: 'h4 mbe18 flex justify-center',
closeButton: styles['close-button-demo'],
}"
@instance="assignDialogRef2"
>
<template #title>
Dialog — Custom Close Button
</template>
<p
class="mbs16 mbe16"
id="dialog-example-description"
>
For the cancel button we have used an AgnosticUI <code>Button</code> of type <code>type="faux</code>
This generates a div that looks like a button. As <code>vue-a11y-dialog</code> generates its own
button around <code>closeButtonContent</code>, this prevents an unwanted nested buttons situation.
</p>
<p class="mbe16">
You'll also notice that this dialog did not “slide up” or “fade in”
as we did NOT pass in either <code>:is-animation-fade-in="true"</code> or <code>:is-animation-slide-up="true"</code>.
Both of these default to <code>false</code>.
</p>
<p class="mbe16">
Lastly, you'll note that the role is <code>alertdialog</code> which results in opting out of
ESC closing the dialog.
</p>
<form class="dialog-form-demo">
<Input
:is-rounded="true"
label="Email (required)"
type="email"
name="EMAIL"
id="email"
placeholder="email@example.com"
required
/>
<div class="mbe16" />
<Button
type="submit"
mode="primary"
:is-rounded="true"
:is-block="true"
>
Sign Up
</Button>
</form>
<template #closeButtonContent>
<Button
type="faux"
:is-rounded="true"
:is-bordered="true"
:is-block="true"
>
Cancel
</Button>
</template>
</Dialog>
</div>
</section>
</template>
<script setup>
import { useCssModule } from "vue";
// Components CSS
import "agnostic-vue/dist/index.css";
import { Button, Dialog, Input } from "agnostic-vue";
const styles = useCssModule();
let dialog = null;
let dialog2 = null;
const openDialog = () => {
console.log("openDialog");
if (dialog) {
dialog.show();
}
};
const openDialog2 = () => {
console.log("openDialog2");
if (dialog2) {
dialog2.show();
}
};
const assignDialogRef = (instance) => {
console.log("assignDialogRef called...");
dialog = instance;
};
const assignDialogRef2 = (instance) => {
console.log("assignDialogRef2 called...");
dialog2 = instance;
};
</script>
<style module>
.close-button-demo {
background-color: transparent;
border: transparent;
width: 100%;
margin-block-start: 0.5rem;
}
.my-dialog-container {
display: flex;
z-index: 2;
/* This is just to override the silly centered app demo css :) */
text-align: left;
}
.my-dialog-overlay {
background-color: rgba(43, 46, 56, 0.9);
animation: fade-in 200ms both;
}
.my-dialog-overlay,
.my-dialog-container {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
/* Crucial—dialog w/not hide visually without this rule */
.my-dialog-container[aria-hidden="true"] {
display: none;
}
.my-dialog-content {
background-color: rgb(255, 255, 255);
margin: auto;
z-index: 2;
position: relative;
padding-block-start: 1.5rem;
padding-block-end: 2rem;
padding-inline-start: 2em;
padding-inline-end: 2rem;
max-width: 90%;
width: 600px;
border-radius: 2px;
}
.my-close-button {
display: inline-block;
cursor: pointer;
padding-inline-start: 2rem;
padding-inline-end: 2rem;
background-color: transparent;
border-radius: 0.1875rem;
border: 1px solid #036dc9;
color: #036dc9;
line-height: 2rem;
text-align: center;
}
.my-dialog-title {
font-size: 2rem;
font-weight: 300;
letter-spacing: 0.005em;
margin-block-start: 0.5rem;
margin-block-end: 0.25rem;
}
</style>
Vue 3: component source, examples
Svelte
View source
Please consider Svelte Dialog experimental and not yet ready for production until we can add missing tests — tl;dr is we'd like to write tests utilizing Cypress's component testing framework but we need to await an upcoming Vite + Cypress plugins to do so.
In your main app.html
, add a container where your dialog will be rendered into — dialog-root
in this example:
<!DOCTYPE html>
<html>
<body>
<div id="svelte">%svelte.body%</div>
<div id="dialog-root"></div>
</body>
</html>
<script>
import 'agnostic-svelte/css/common.min.css';
import { Dialog } from "agnostic-svelte";
let dialogInstance;
const assignDialogInstance = (ev) => {
dialogInstance = ev.detail.instance;
};
const openDialog = () => {
if (dialogInstance) {
dialogInstance.show();
}
};
</script>
<div class="container flex flex-column items-center">
<h3 class="mbe24">Dialog</h3>
<p class="mbe24">
The following opens because we've assigned a dialog <code>ref</code>:
</p>
<Button
mode="primary"
isBlock
isBordered
isRounded
type="button"
on:click={openDialog}
>
Open dialog via dialogRef
</Button>
<Dialog id="a11y-dialog"
dialogRoot="#dialog-root"
closeButtonLabel="My close button label"
closeButtonPosition="last"
titleId="uniqueTitleId"
role="dialog"
classNames={{
title: 'h4 mbe18 flex justify-center'
}}
isAnimationFadeIn
isAnimationSlideUp
on:instance={assignDialogInstance}
>
<div name="title">
My Dialog
</div>
<p
class="mbs16 mbe16"
id="dialog-example-description"
>
Fill in the form below to receive our newsletter!
</p>
<form class="dialog-form-demo">
<Input
isRounded
label="Email (required)"
type="email"
name="EMAIL"
id="email"
placeholder="email@example.com"
required
/>
<div class="mbe16" />
<Button
type="submit"
mode="primary"
isRounded
isBlock
>
Sign Up
</Button>
</form>
</Dialog>
</div>
Svelte: component source
Angular (Experimental)
Please consider Angular Dialog experimental and not yet ready for production
View source
In your Angular configuration (likely angular.json
) ensure you're including the common AgnosticUI styles:
"styles": ["agnostic-angular/common.min.css"],
Add AgnosticUI's AgModule
module:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AgModule } from 'agnostic-angular';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, AgModule],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
Now you can use in your components:
import { Component } from '@angular/core';
@Component({
selector: 'your-component',
template: `
<section>
<h2>Dialog</h2>
<ag-button mode="primary"
[isBordered]="true"
[isBlock]="true"
[isRounded]="true"
type="button"
data-test-id="dataA11yBtn"
data-a11y-dialog-show="a11y-dialog">
Open the dialog via data attribute
</ag-button>
<div class="mbe16"></div>
<ag-button mode="primary"
[isBordered]="true"
[isBlock]="true"
[isRounded]="true"
(click)="openDialog()"
type="button">
Open dialog via instance
</ag-button>
<ng-template #main>
<p class="mbs16 mbe16"
id="dialog-example-description">
Fill in the form below to receive our newsletter!
</p>
<form class="dialog-form-demo">
<ag-input [isRounded]="true"
label="Email (required)"
type="email"
name="EMAIL"
id="email"
placeholder="email@example.com"
required></ag-input>
<div class="mbe16"></div>
<ag-button type="submit"
mode="primary"
[isRounded]="true"
[isBlock]="true">Sign Up</ag-button>
</form>
</ng-template>
<ng-template #title>
<span data-test-id="dialogTitle">A11yDialog Test</span>
</ng-template>
<ng-template #closeButtonFirst>
<span>Close (only appears if closeButtonPosition="first" but that's the default)</span>
</ng-template>
<ng-template #closeButtonLast>
<ag-close [isFaux]="true"
size="xlarge"></ag-close>
</ng-template>
<ag-dialog id="a11y-dialog"
appRoot="#main"
dialogRoot="#dialog-root"
[isAnimationFadeIn]="true"
[isAnimationSlideUp]="true"
closeButtonPosition="last"
(instance)="assignDialogInstance($event)"
[closeButtonFirstTemplate]="closeButtonFirst"
[closeButtonLastTemplate]="closeButtonLast"
[mainTemplate]="main"
[titleTemplate]="title">
</ag-dialog>
</section>
<section>
<h2>Dialog 2</h2>
<ag-button mode="primary"
[isBordered]="true"
[isBlock]="true"
[isRounded]="true"
type="button"
data-a11y-dialog-show="a11y-dialog2">
Open dialog 2
</ag-button>
<ng-template #main2>
<p class="mbs16 mbe16"
id="dialog-example-description">
The <code>close-button-demo</code> class is in App.css (for the Cancel button at bottom).
Otherwise, we use an AgnosticUI button of <code>type="faux</code> which generates a div that
looks like a button. As <code>angular-a11y-dialog</code> generates its own button
around <code>closeButtonContent</code>, this prevents an unwanted nested buttons situation.
</p>
<p class="mbe16">
You'll also notice that this dialog did not <span class="quoted">slide up</span> or <span class="quoted">fade in</span>
as we did NOT pass in either <code>isAnimationFadeIn</code> or <code>isAnimationSlideUp</code>.
Both of these default to <code>false</code>.
</p>
<form class="dialog-form-demo">
<ag-input [isRounded]="true"
label="Email (required)"
type="email"
name="EMAIL"
id="email"
placeholder="email@example.com"
required></ag-input>
<div class="mbe16"></div>
<ag-button type="submit"
mode="primary"
[isRounded]="true"
[isBlock]="true">Sign Up</ag-button>
</form>
</ng-template>
<ng-template #title2>Dialog — Custom Close Button</ng-template>
<ng-template #closeButtonLast2>
<ag-button type="faux"
[isRounded]="true"
[isBordered]="true"
[isBlock]="true">Cancel</ag-button>
</ng-template>
<ag-dialog id="a11y-dialog2"
appRoot="#main"
dialogRoot="#dialog-root"
closeButtonPosition="last"
[classNames]="{
container: 'my-dialog-container',
overlay: 'my-dialog-overlay',
document: 'my-dialog-content',
title: 'h4 mbe18',
closeButton: 'close-button-demo'
}"
[closeButtonLastTemplate]="closeButtonLast2"
[mainTemplate]="main2"
[titleTemplate]="title2">
</ag-dialog>
</section>`
})
export class YourComponent {
dialogInstance!: any;
openDialog() {
this.dialogInstance.show();
}
assignDialogInstance(instance: any) {
this.dialogInstance = instance;
};
}
Angular: component source, example use
Storybook
You can run the framework Storybooks and see live examples for React, Vue 3, Svelte, Astro, and Angular (experimental). The following will set up Storybook and run locally:
How to run Storybook
git clone git@github.com:AgnosticUI/agnosticui.git
cd agnosticui/<PACKAGE_NAME> && npm i # e.g. cd agnosticui/agnostic-react && npm i
npm run storybook
See Running Storybook.