Skip to main content
0.20.0
View Zag.js on Github
Join the Discord server

Dialog

A dialog is a window overlaid on either the primary window or another dialog window. Content behind a modal dialog is inert, meaning that users cannot interact with it.

Properties

Features

  • Supports modal and non-modal modes.
  • Focus is trapped and scrolling is blocked in the modal mode.
  • Provides screen reader announcements via rendered title and description.
  • Pressing Esc closes the dialog.

Installation

To use the dialog machine in your project, run the following command in your command line:

npm install @zag-js/dialog @zag-js/react # or yarn add @zag-js/dialog @zag-js/react

This command will install the framework agnostic dialog logic and the reactive utilities for your framework of choice.

Anatomy

To use the dialog component correctly, you'll need to understand its anatomy and how we name its parts.

Each part includes a data-part attribute to help identify them in the DOM.

The dialog pattern consists of:

  • Trigger: The button that triggers the dialog
  • Backdrop: The dim background overlay that's usually behind the dialog element.
  • Container: The container for the dialog's content.
  • Content: Used to house the content of the dialog.

The dialog's content consists of:

  • Title: The title of the dialog.
  • Description: The description that supports the title.
  • Close Button: The button used to close the dialog.

Usage

First, import the dialog package into your project

import * as dialog from "@zag-js/dialog"

The dialog package exports two key functions:

  • machine — The state machine logic for the dialog widget as described in WAI-ARIA specification.
  • connect — The function that translates the machine's state to JSX attributes and event handlers.

You'll need to provide a unique id to the useMachine hook. This is used to ensure that every part has a unique identifier.

Next, import the required hooks and functions for your framework and use the dialog machine in your project 🔥

import * as dialog from "@zag-js/dialog" import { useMachine, normalizeProps, Portal } from "@zag-js/react" export function Dialog() { const [state, send] = useMachine(dialog.machine({ id: "1" })) const api = dialog.connect(state, send, normalizeProps) return ( <> <button {...api.triggerProps}>Open Dialog</button> {api.isOpen && ( <Portal> <div {...api.backdropProps} /> <div {...api.containerProps}> <div {...api.contentProps}> <h2 {...api.titleProps}>Edit profile</h2> <p {...api.descriptionProps}> Make changes to your profile here. Click save when you are done. </p> <div> <input placeholder="Enter name..." /> <button>Save</button> </div> <button {...api.closeTriggerProps}>Close</button> </div> </div> </Portal> )} </> ) }

Managing focus within the dialog

When the dialog opens, it automatically sets focus on the first focusable elements and traps focus within it, so that tabbing is constrained to it.

To control the element that should receive focus on open, pass the initialFocusEl context (which can be an element or a function that returns an element)

export function Dialog() { // initial focused element ref const inputRef = useRef(null) const [state, send] = useMachine( dialog.machine({ initialFocusEl: () => inputRef.current, }), ) // ... return ( //... <input ref={inputRef} /> // ... ) }

To set the element that receives focus when the dialog closes, pass the finalFocusEl in the similar fashion as shown above.

Closing the dialog on overlay click

By default, the dialog closes when you click its overlay. You can set closeOnOutsideClick to false if you want the modal to stay visible.

const [state, send] = useMachine( dialog.machine({ closeOnOutsideClick: false, }), )

Listening for open state changes

When the dialog is opened or closed, the onOpenChange callback is invoked.

const [state, send] = useMachine( dialog.machine({ onOpenChange(details) { // details => { open: boolean } console.log("open:", details.open) }, }), )

Controlling the scroll behavior

When the dialog is open, it prevents scrolling on the body element. To disable this behavior, set the preventScroll context to false.

const [state, send] = useMachine( dialog.machine({ preventScroll: false, }), )

Creating an alert dialog

The dialog has support for dialog and alert dialog roles. It's set to dialog by default. To change it's role, pass the role: alertdialog property to the machine's context.

That's it! Now you have an alert dialog.

const [state, send] = useMachine( dialog.machine({ role: "alertdialog", }), )

By definition, an alert dialog will contain two or more action buttons. We recommended setting focus to the least destructive action via initialFocusEl

Styling guide

Earlier, we mentioned that each accordion part has a data-part attribute added to them to select and style them in the DOM.

[data-part="trigger"] { /* styles for the trigger element */ } [data-part="backdrop"] { /* styles for the backdrop element */ } [data-part="container"] { /* styles for the container element */ } [data-part="content"] { /* styles for the content element */ } [data-part="title"] { /* styles for the title element */ } [data-part="description"] { /* styles for the description element */ } [data-part="close-trigger"] { /* styles for the close trigger element */ }

Open and closed state

The dialog has two states: open and closed. You can use the data-state attribute to style the dialog or trigger based on its state.

[data-part="content"][data-state="open|closed"] { /* styles for the open state */ } [data-part="trigger"][data-state="open|closed"] { /* styles for the open state */ }

Methods and Properties

The dialog's api exposes the following methods:

  • isOpenbooleanWhether the dialog is open
  • open() => voidFunction to open the dialog
  • close() => voidFunction to close the dialog

Edit this page on GitHub

On this page