CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-zag-js--dialog

Core logic for the dialog widget implemented as a state machine

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

index.mddocs/

Zag.js Dialog

Zag.js Dialog provides a headless, framework-agnostic dialog (modal) component implemented as a finite state machine. It delivers accessible dialog functionality following WAI-ARIA authoring practices, with complete keyboard interactions, focus management, and ARIA roles/attributes handled automatically.

Package Information

  • Package Name: @zag-js/dialog
  • Package Type: npm
  • Language: TypeScript
  • Installation: npm install @zag-js/dialog

Core Imports

import { machine, connect, anatomy, props, splitProps } from "@zag-js/dialog";
import type { Api, Props, Service, ElementIds, OpenChangeDetails } from "@zag-js/dialog";
import type { FocusOutsideEvent, InteractOutsideEvent, PointerDownOutsideEvent } from "@zag-js/dialog";

For CommonJS:

const { machine, connect, anatomy, props, splitProps } = require("@zag-js/dialog");

Basic Usage

import { machine, connect } from "@zag-js/dialog";
import { normalizeProps } from "@zag-js/react"; // or your framework adapter

function MyDialog() {
  // Create machine instance
  const [state, send] = useMachine(
    machine({
      id: "dialog-1",
      onOpenChange: (details) => {
        console.log("Dialog open:", details.open);
      },
    })
  );

  // Connect machine to UI
  const api = connect(state, normalizeProps);

  return (
    <div>
      <button {...api.getTriggerProps()}>Open Dialog</button>
      
      {api.open && (
        <div {...api.getPositionerProps()}>
          <div {...api.getBackdropProps()} />
          <div {...api.getContentProps()}>
            <h2 {...api.getTitleProps()}>Dialog Title</h2>
            <p {...api.getDescriptionProps()}>Dialog content goes here</p>
            <button {...api.getCloseTriggerProps()}>Close</button>
          </div>
        </div>
      )}
    </div>
  );
}

Architecture

Zag.js Dialog is built around several key components:

  • State Machine: Core logic implemented as a finite state machine with states (open/closed), events, effects, and actions
  • Connect API: Framework-agnostic connection layer that provides prop functions for UI elements
  • Anatomy System: Structured component parts with consistent naming and behavior
  • Accessibility Layer: Built-in ARIA roles, keyboard interactions, and focus management
  • Framework Adapters: Integration with React, Vue, Solid, Svelte through separate adapter packages

Capabilities

Dialog State Machine

Core state machine that manages dialog open/closed states, handles events, and orchestrates all dialog behaviors including accessibility features.

function machine(props: Props): Machine;

interface Machine extends StateMachine<DialogSchema> {
  state: "open" | "closed";
  send: (event: MachineEvent) => void;
}

interface MachineEvent {
  type: "OPEN" | "CLOSE" | "TOGGLE" | "CONTROLLED.OPEN" | "CONTROLLED.CLOSE";
}

Dialog State Machine

API Connection

Connection layer that transforms state machine into framework-agnostic prop functions for UI elements, providing complete accessibility and interaction handling.

function connect<T extends PropTypes>(
  service: Service<DialogSchema>,
  normalize: NormalizeProps<T>
): Api<T>;

interface Api<T extends PropTypes> {
  open: boolean;
  setOpen: (open: boolean) => void;
  getTriggerProps: () => T["button"];
  getBackdropProps: () => T["element"];
  getPositionerProps: () => T["element"];
  getContentProps: () => T["element"];
  getTitleProps: () => T["element"];
  getDescriptionProps: () => T["element"];
  getCloseTriggerProps: () => T["button"];
}

API Connection Methods

Component Anatomy

Anatomical structure defining the dialog's component parts with consistent naming and CSS class generation.

interface Anatomy {
  trigger: AnatomyPart;
  backdrop: AnatomyPart;
  positioner: AnatomyPart;
  content: AnatomyPart;
  title: AnatomyPart;
  description: AnatomyPart;
  closeTrigger: AnatomyPart;
}

const anatomy: Anatomy;

Event Types

Event types for dismissible interactions that can occur outside the dialog.

type FocusOutsideEvent = CustomEvent & {
  target: Element;
  preventDefault: () => void;
};

type InteractOutsideEvent = CustomEvent & {
  target: Element;
  preventDefault: () => void;
};

type PointerDownOutsideEvent = CustomEvent & {
  target: Element;
  preventDefault: () => void;
};

Props Utilities

Utility functions for working with dialog props in framework integrations.

/**
 * Props utility for type-safe prop definitions
 */
const props: PropsDefinition<Props>;

/**
 * Split props utility for separating dialog props from other props
 * @param allProps - Object containing all props
 * @returns Tuple with [dialogProps, otherProps]
 */
const splitProps: <T extends Partial<Props>>(
  allProps: T & Record<string, any>
) => [T, Record<string, any>];

interface PropsDefinition<T> {
  /** Array of prop keys for type validation */
  keys: (keyof T)[];
  /** Type-safe prop object */
  object: T;
}

Types

Configuration Props

interface Props {
  // Element IDs
  ids?: ElementIds;
  
  // State Management
  open?: boolean;
  defaultOpen?: boolean;
  onOpenChange?: (details: OpenChangeDetails) => void;
  
  // Behavior Configuration
  modal?: boolean;
  trapFocus?: boolean;
  preventScroll?: boolean;
  restoreFocus?: boolean;
  closeOnInteractOutside?: boolean;
  closeOnEscape?: boolean;
  
  // Accessibility
  role?: "dialog" | "alertdialog";
  "aria-label"?: string;
  
  // Focus Management
  initialFocusEl?: () => MaybeElement;
  finalFocusEl?: () => MaybeElement;
  
  // Event Handlers
  onInteractOutside?: (event: InteractOutsideEvent) => void;
  onFocusOutside?: (event: FocusOutsideEvent) => void;
  onPointerDownOutside?: (event: PointerDownOutsideEvent) => void;
  onEscapeKeyDown?: (event: KeyboardEvent) => void;
  onRequestDismiss?: () => void;
  
  // Other
  dir?: "ltr" | "rtl";
  id?: string;
  getRootNode?: () => Node | ShadowRoot | Document;
  persistentElements?: () => Element[];
}

interface ElementIds {
  trigger?: string;
  positioner?: string;
  backdrop?: string;
  content?: string;
  closeTrigger?: string;
  title?: string;
  description?: string;
}

interface OpenChangeDetails {
  open: boolean;
}

type MaybeElement = Element | null | undefined;

Service Types

interface Service extends StateMachineService<DialogSchema> {
  state: State;
  send: (event: MachineEvent) => void;
  context: Context;
  prop: (key: string) => any;
  scope: Scope;
}

interface DialogSchema {
  props: Props;
  state: "open" | "closed";
  context: {
    rendered: { title: boolean; description: boolean };
  };
  guard: "isOpenControlled";
  effect: "trackDismissableElement" | "preventScroll" | "trapFocus" | "hideContentBelow";
  action: "checkRenderedElements" | "syncZIndex" | "invokeOnClose" | "invokeOnOpen" | "toggleVisibility";
  event: MachineEvent;
}

docs

connect.md

index.md

machine.md

tile.json