Skip to content

Dialog (Alpine)

The dialog component informs users about a task and can contain critical information, require decisions, or involve multiple tasks. The Dialog component can be used to create a modal, alert, or other custom dialog and can be fully customized to fit your needs.

No content has been provided for this dialog.
<Dialog />

API Reference

This component is based on the <dialog> HTML element. The following are the props that can be passed to the component.

Prop Type Default
hideClose
boolean false
triggerProps
object
variant
"contained" | "outlined" | "subtle" | "text" "contained"
size
"sm" | "md" | "lg" "md"
title
string
icon
boolean false
href
string
disabled
boolean false

Accessibility

The <Dialog> component leverages the <dialog> element which is natively accessible. The following are some additional accessibility features that are built into the component:

  • Clicking off from the dialog will close it. This is done by listening for a click event on the dialog element and checking if the target is the dialog itself.
  • When the dialog element is opened, the body prevents scrolling of the page. This is done by using x-trap.noscroll and having it track the open state of the dialog element.
    • This feature requires the Alpine.js Focus Plugin to be installed and configured in order to work.
  • When the dialog includes a form, we suggest passing autofocus to the first input field to ensure that the user can start interacting with the form immediately.
  • Ensure forms are validated before submitting. This can be done by adding the required attribute to the input fields and setting the formnovalidate attribute on the cancel button.

Examples

Hide Close Button

You can hide the default close button by passing the hideClose prop to the <Dialog> component.

No content has been provided for this dialog.
<Dialog hideClose />

Form Dialog

You can use a dialog to handle form data. The following example demonstrates how to create a custom form dialog using the <Dialog> component.

---
import Button from '@/components/Button.astro'
import TextInput from '@/components/forms/TextInput.astro'
---
<Dialog>
<form method="dialog" class="grid gap-24">
<div class="flex gap-16 p-2">
<TextInput
label="First Name"
name="first-name"
placeholder="John"
required
inputProps={{ autofocus: true }}
/>
<TextInput
label="Last Name"
name="last-name"
placeholder="Smith"
required
/>
</div>
<footer class="flex items-center justify-end gap-16">
<Button
type="submit"
variant="outlined"
data-dialog="close"
formnovalidate
>Cancel</Button>
<Button type="submit">Submit</Button>
</footer>
</form>
</Dialog>

Custom Trigger & Dialog

The dialog element can be customized to fit whatever context it is being used in. The following example demonstrates how to create a custom trigger and dialog using the <Dialog> component. In addition, the dialog element uses a named slot of "trigger" for the content of the button. Any additional elements placed within the dialog will be rendered as the dialog content.

Culpa proident non exercitation eu consequat Lorem ipsum.

<Dialog
class="rounded-lg border-2 border-white bg-gradient-to-r from-indigo-500 from-10% via-sky-500 via-30% to-emerald-500 to-90% p-24"
triggerProps={{class:"rounded bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500 p-8 font-bold !text-white transition hover:bg-gradient-to-tr hover:opacity-85 active:opacity-75"}}
>
<span slot="trigger">Open Custom Dialog</span>
<div class="flex items-center">
Culpa proident non exercitation eu consequat Lorem ipsum.
</div>
</Dialog>

Astro Component

It’s recommended that an element like this is made as a reusable component. In this case, we have put together the following Astro component using Alpine.js to serve as a blueprint for how you could possibly set up a Dialog component, and what properties to consider.

Dialog.astro
---
import type { HTMLAttributes, ComponentProps } from 'astro/types'
import Button from '@/components/elements/Button.astro'
import close from '@/assets/svg/close.svg?raw'
interface Props extends HTMLAttributes<'dialog'> {
/*
Whether or not the close button should be hidden.
*/
hideClose?: boolean
/*
The trigger button props.
*/
triggerProps?: ComponentProps<typeof Button>
}
const {
hideClose = false,
triggerProps,
class: className,
...attrs
} = Astro.props
---
<div
data-component="Dialog"
x-data=`{
open: false,
// Close the dialog when the user clicks backdrop
handleDialogClick(event) {
(event.target === $refs.dialogRef) && this.handleDialogClose()
},
// Delay close to allow for animation
handleDialogClose() {
if (!this.open) return
this.open = false
$refs.dialogRef.dataset.dialogStatus = 'closing'
setTimeout(() => {
delete $refs.dialogRef.dataset.dialogStatus
$refs.dialogRef.close()
}, 300);
}
}`
>
<!-- trigger -->
<Button @click="$refs.dialogRef.showModal(), open = true" {...triggerProps}
><slot name="trigger">Open Dialog</slot></Button
>
<!-- dialog -->
<dialog
x-ref="dialogRef"
x-trap.noscroll="open"
@keydown.escape.prevent="handleDialogClose()"
@click="handleDialogClick(event)"
class:list={['dialog', className]}
{...attrs}
>
{
!hideClose && (
<Button
icon
size="sm"
title="Close"
variant="subtle"
class="absolute right-4 top-4 size-24 !min-h-0 [&_svg]:size-12"
@click="handleDialogClose()"
>
<Fragment set:html={close} />
Close
</Button>
)
}
<slot>
<div class="flex items-center">
No content has been provided for this dialog.
</div>
</slot>
</dialog>
</div>