How to Implement Dialog Patterns with Astryx Dialog and Modal Components

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 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 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 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:

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/packages/core/src/Dialog/Dialog.tsx)

Form Dialog with Controlled Dismissal

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

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/packages/core/src/Dialog/DialogHeader.tsx)

Full-Screen Modal Experience

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

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/packages/core/src/Dialog/Dialog.tsx)

Positioned and Anchored Dialogs

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

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/packages/core/src/Dialog/Dialog.tsx)

Summary

  • Unified Component: Astryx uses a single Dialog component in 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →