# How to Implement Dialog Patterns with Astryx Dialog and Modal Components

> Learn to implement dialog patterns with Astryx Dialog and Modal components. Build pop-ups to full-screen modals with this flexible, native dialog solution.

- Repository: [Meta/astryx](https://github.com/facebook/astryx)
- Tags: how-to-guide
- Published: 2026-08-05

---

**Astryx provides a single flexible Dialog component built on the native `<dialog>` element that supports everything from simple informational pop-ups to full-screen modal experiences through configurable variants and purpose-driven dismissal logic.**

The Astryx design system from Facebook offers a powerful approach to modal UI patterns through its unified Dialog architecture. Located in the `facebook/astryx` repository, this component leverages the browser's native dialog capabilities while providing React-specific optimizations for focus management, scroll locking, and accessibility. Understanding how to configure its variants and purpose props allows you to implement any dialog pattern—from lightweight confirmations to complex full-screen workflows—without managing separate modal components.

## Core Architecture and Component Structure

### Dialog.tsx – The Root Implementation

The [`Dialog.tsx`](https://github.com/facebook/astryx/blob/main/Dialog.tsx) file serves as the main component orchestrating the entire modal lifecycle. It handles open/close transitions, body scroll locking, animation origins, and variant-specific styling. The component utilizes the native `showModal()` method to automatically enable focus trapping and backdrop inertness, eliminating the need for custom focus management code.

Key responsibilities include managing the `DialogContext` provider, which shares the dialog's internal state with child components, and implementing the `purpose` prop that governs dismissal behavior.

### DialogHeader.tsx – Accessible Title Management

The [`DialogHeader.tsx`](https://github.com/facebook/astryx/blob/main/DialogHeader.tsx) component automatically wires dialog titles to accessibility attributes. When rendered inside a Dialog, it generates a unique `titleId` and registers it with `DialogContext`, which the root dialog element then applies as `aria-labelledby`. This ensures screen readers correctly announce the dialog's purpose immediately upon opening.

The header also provides an optional close button that invokes the `onOpenChange(false)` handler supplied by the parent component.

### DialogContext.ts – Internal State Sharing

[`DialogContext.ts`](https://github.com/facebook/astryx/blob/main/DialogContext.ts) supplies child components with critical dialog metadata, including the `titleId` for accessibility linking and an `isInline` flag that indicates whether the dialog renders as a true modal or inline preview. Components like `DialogHeader` consume this context to adjust their behavior accordingly.

### Utility Hooks

The Dialog system includes `useScrollLock` to prevent background scrolling while modals are open, particularly addressing iOS Safari limitations. For testing scenarios, `useImperativeDialog` exposes imperative `showModal()` and `close()` methods, though React state controlled via `onOpenChange` is recommended for production implementations.

## Configuring Dialog Variants and Purposes

### Standard vs. Fullscreen Variants

The `variant` prop accepts two values that dramatically alter the dialog's presentation:

- **`standard`** (default): Configurable via `width`, `maxHeight`, and `position` props, allowing precise dimension control and static positioning.
- **`fullscreen`**: Occupies the entire viewport, automatically ignoring `width`, `height`, and `position` settings. Ideal for mobile-first experiences or immersive onboarding flows.

### Purpose-Driven Dismissal Behavior

The `purpose` prop determines how users can dismiss the dialog, creating three distinct interaction patterns:

- **`info`**: Allows both Escape key and backdrop click dismissal (default behavior).
- **`form`**: Permits Escape key dismissal but blocks backdrop clicks, preventing accidental data loss while maintaining keyboard accessibility.
- **`required`**: Disables all automatic dismissal methods—both Escape and backdrop clicks are blocked. Use for security confirmations or permission dialogs that demand explicit user action.

### Static Positioning

For non-modal or anchored dialogs, supply a `position` object with `top`, `bottom`, `start`, or `end` values. The `resolveDialogPositionOffsets` utility translates these logical properties into dynamic CSS positioning, though this is ignored when `variant="fullscreen"`.

## Accessibility Implementation

Astryx Dialogs inherit robust accessibility from the native `<dialog>` element foundation. The component automatically applies `aria-modal="true"` and, when `purpose="required"` is set, assigns `role="alertdialog"` to signal that the user must interact with the dialog before continuing.

When using `DialogHeader`, the automatic `aria-labelledby` wiring ensures the dialog's accessible name matches its visible title. Developers can override this by supplying explicit `aria-label` or `aria-labelledby` props directly to the Dialog component.

## Code Examples for Common Dialog Patterns

### Informational Confirmation Dialog

Implement a standard confirmation dialog that closes via Escape or backdrop click:

```tsx
import { useState } from 'react';
import { Dialog, DialogHeader } from '@astryxdesign/core';

export function InfoDialogDemo() {
  const [open, setOpen] = useState(false);
  return (
    <>
      <button onClick={() => setOpen(true)}>Open dialog</button>

      <Dialog
        isOpen={open}
        onOpenChange={setOpen}
        width={400}
        maxHeight="60vh"
        purpose="info"
      >
        <DialogHeader title="Info" onOpenChange={setOpen} />
        <div style={{ padding: 16 }}>
          This is an informational dialog. Click outside or press Escape to close.
        </div>
      </Dialog>
    </>
  );
}

```

*Source:* [[`Dialog.tsx`](https://github.com/facebook/astryx/blob/main/Dialog.tsx)](https://github.com/facebook/astryx/blob/main/packages/core/src/Dialog/Dialog.tsx)

### Form Dialog with Controlled Dismissal

Create a form dialog that prevents accidental backdrop dismissal while maintaining keyboard accessibility:

```tsx
import { useState } from 'react';
import { Dialog, DialogHeader } from '@astryxdesign/core';
import { Layout, LayoutContent, LayoutFooter } from '@astryxdesign/core';

export function FormDialogDemo() {
  const [open, setOpen] = useState(false);
  return (
    <>
      <button onClick={() => setOpen(true)}>Edit profile</button>

      <Dialog
        isOpen={open}
        onOpenChange={setOpen}
        purpose="form"
        width={500}
      >
        <DialogHeader title="Edit Profile" onOpenChange={setOpen} />
        <Layout
          content={
            <LayoutContent>
              {/* form fields go here */}
            </LayoutContent>
          }
          footer={
            <LayoutFooter hasDivider>
              <button onClick={() => setOpen(false)}>Save</button>
            </LayoutFooter>
          }
        />
      </Dialog>
    </>
  );
}

```

*Source:* [[`DialogHeader.tsx`](https://github.com/facebook/astryx/blob/main/DialogHeader.tsx)](https://github.com/facebook/astryx/blob/main/packages/core/src/Dialog/DialogHeader.tsx)

### Full-Screen Modal Experience

Implement an immersive full-screen dialog for mobile workflows or complex multi-step processes:

```tsx
import { useState } from 'react';
import { Dialog, DialogHeader } from '@astryxdesign/core';

export function FullscreenDemo() {
  const [open, setOpen] = useState(false);
  return (
    <>
      <button onClick={() => setOpen(true)}>Open fullscreen</button>

      <Dialog
        isOpen={open}
        onOpenChange={setOpen}
        variant="fullscreen"
        purpose="info"
      >
        <DialogHeader title="Full‑Screen Modal" onOpenChange={setOpen} />
        <div style={{ padding: 24 }}>
          <p>Everything fills the viewport.</p>
        </div>
      </Dialog>
    </>
  );
}

```

*Source:* [[`Dialog.tsx`](https://github.com/facebook/astryx/blob/main/Dialog.tsx)](https://github.com/facebook/astryx/blob/main/packages/core/src/Dialog/Dialog.tsx)

### Positioned and Anchored Dialogs

Pin a dialog to specific viewport coordinates for tooltip-style or contextual overlays:

```tsx
import { useState } from 'react';
import { Dialog, DialogHeader } from '@astryxdesign/core';

export function PositionedDemo() {
  const [open, setOpen] = useState(false);
  return (
    <>
      <button onClick={() => setOpen(true)}>Show anchored dialog</button>

      <Dialog
        isOpen={open}
        onOpenChange={setOpen}
        position={{ top: 80, end: 20 }}
        purpose="info"
      >
        <DialogHeader title="Anchored" onOpenChange={setOpen} />
        <div style={{ padding: 12 }}>I’m pinned to the top‑right.</div>
      </Dialog>
    </>
  );
}

```

*Source:* `resolveDialogPositionOffsets` in [[`Dialog.tsx`](https://github.com/facebook/astryx/blob/main/Dialog.tsx)](https://github.com/facebook/astryx/blob/main/packages/core/src/Dialog/Dialog.tsx)

## Summary

- **Unified Component**: Astryx uses a single `Dialog` component in [`packages/core/src/Dialog/Dialog.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/Dialog/Dialog.tsx) to handle all modal patterns, eliminating the need for separate alert, confirm, or modal implementations.
- **Native Foundation**: Built on the HTML `<dialog>` element with `showModal()` for automatic focus trapping and backdrop inertness.
- **Behavioral Control**: The `purpose` prop (`info`, `form`, `required`) precisely controls dismissal methods without custom event handling.
- **Accessibility Automation**: `DialogHeader` automatically wires `aria-labelledby` relationships, while `aria-modal` and `role="alertdialog"` are applied based on configuration.
- **Flexible Positioning**: Support for both fullscreen immersion and static positioning via logical properties (`top`, `end`, etc.).

## Frequently Asked Questions

### What is the difference between the standard and fullscreen variants in Astryx Dialog?

The **standard** variant accepts `width`, `maxHeight`, and `position` props for precise dimension control and flexible placement, while the **fullscreen** variant ignores these properties and expands to fill the entire viewport. Use fullscreen for mobile-first experiences or immersive workflows where content needs maximum screen real estate.

### How does the purpose prop control dialog dismissal behavior?

The `purpose` prop creates three security levels: **`info`** permits both Escape key and backdrop click dismissal; **`form`** allows Escape but blocks backdrop clicks to prevent accidental data loss; and **`required`** blocks both methods, forcing users to interact with specific UI elements (like a confirmation button) to close the dialog.

### Can I programmatically control the Dialog without using React state?

While the primary API uses React state via `isOpen` and `onOpenChange`, the `useImperativeDialog` hook (primarily for test utilities) exposes imperative `showModal()` and `close()` methods. However, the declarative state pattern is strongly recommended for production code to maintain React's predictable data flow.

### How does Astryx ensure accessibility in Dialog components?

Astryx leverages the native `<dialog>` element's built-in accessibility features, including automatic focus trapping and inert backdrop handling. The component applies `aria-modal="true"` by default and `role="alertdialog"` when `purpose="required"`. When using `DialogHeader`, the system automatically generates and associates a unique `titleId` via `aria-labelledby`, ensuring screen readers announce the dialog's title immediately upon opening.