Core logic for the dialog widget implemented as a state machine
The connect function transforms the dialog state machine into framework-agnostic prop functions that provide complete accessibility, interaction handling, and ARIA attributes for UI elements.
Connects a dialog service to UI framework normalization, returning an API object with prop functions for each dialog element.
/**
* Connects dialog service to UI framework, returning prop functions
* @param service - Dialog state machine service
* @param normalize - Framework-specific prop normalization function
* @returns API object with prop functions for dialog elements
*/
function connect<T extends PropTypes>(
service: Service,
normalize: NormalizeProps<T>
): Api<T>;
interface Service {
/** Current machine state */
state: State;
/** Send events to machine */
send: (event: MachineEvent) => void;
/** Machine context */
context: Context;
/** Get prop value */
prop: (key: string) => any;
/** DOM scope for element access */
scope: Scope;
}
interface Api<T extends PropTypes> {
/** Current open state */
open: boolean;
/** Programmatically set open state */
setOpen: (open: boolean) => void;
// Element prop functions
getTriggerProps: () => T["button"];
getBackdropProps: () => T["element"];
getPositionerProps: () => T["element"];
getContentProps: () => T["element"];
getTitleProps: () => T["element"];
getDescriptionProps: () => T["element"];
getCloseTriggerProps: () => T["button"];
}Usage Example:
import { machine, connect } from "@zag-js/dialog";
import { normalizeProps } from "@zag-js/react";
const service = useMachine(machine({ id: "dialog-1" }));
const api = connect(service, normalizeProps);
// Use api.getTriggerProps(), api.getContentProps(), etc.Direct access to dialog open state and programmatic control.
interface OpenStateAPI {
/** Whether dialog is currently open */
open: boolean;
/**
* Set dialog open state programmatically
* @param open - True to open dialog, false to close
*/
setOpen: (open: boolean) => void;
}Usage Examples:
// Check if dialog is open
if (api.open) {
console.log("Dialog is currently open");
}
// Open dialog programmatically
api.setOpen(true);
// Close dialog programmatically
api.setOpen(false);
// Toggle dialog
api.setOpen(!api.open);Props for the button that opens the dialog, with proper ARIA attributes and click handling.
/**
* Get props for dialog trigger button
* @returns Button props with accessibility and click handling
*/
getTriggerProps(): ButtonProps;
interface ButtonProps {
/** Element ID */
id: string;
/** Button type */
type: "button";
/** Text direction */
dir: "ltr" | "rtl";
/** ARIA popup type */
"aria-haspopup": "dialog";
/** ARIA expanded state */
"aria-expanded": boolean;
/** Data state attribute */
"data-state": "open" | "closed";
/** Controls relationship */
"aria-controls": string;
/** Click handler */
onClick: (event: MouseEvent) => void;
}Usage Example:
<button {...api.getTriggerProps()}>
Open Dialog
</button>Props for the modal backdrop that appears behind the dialog content.
/**
* Get props for dialog backdrop
* @returns Element props for backdrop
*/
getBackdropProps(): ElementProps;
interface ElementProps {
/** Element ID */
id: string;
/** Text direction */
dir: "ltr" | "rtl";
/** Hidden state */
hidden: boolean;
/** Data state attribute */
"data-state": "open" | "closed";
}Usage Example:
<div {...api.getBackdropProps()} />Props for the element that positions the dialog content, typically a full-screen container.
/**
* Get props for dialog positioner
* @returns Element props for positioner container
*/
getPositionerProps(): PositionerProps;
interface PositionerProps {
/** Element ID */
id: string;
/** Text direction */
dir: "ltr" | "rtl";
/** CSS styles */
style: {
pointerEvents: "none" | undefined;
};
}Usage Example:
<div {...api.getPositionerProps()}>
{/* Dialog content container */}
</div>Props for the main dialog content container with full accessibility attributes.
/**
* Get props for dialog content container
* @returns Element props with full ARIA attributes
*/
getContentProps(): ContentProps;
interface ContentProps {
/** Element ID */
id: string;
/** Text direction */
dir: "ltr" | "rtl";
/** ARIA role */
role: "dialog" | "alertdialog";
/** Hidden state */
hidden: boolean;
/** Tab index for focus */
tabIndex: -1;
/** Data state attribute */
"data-state": "open" | "closed";
/** Modal flag */
"aria-modal": true;
/** Accessible label */
"aria-label"?: string;
/** Labeled by title element */
"aria-labelledby"?: string;
/** Described by description element */
"aria-describedby"?: string;
}Usage Example:
<div {...api.getContentProps()}>
<h2 {...api.getTitleProps()}>Dialog Title</h2>
<p {...api.getDescriptionProps()}>Dialog description</p>
{/* Dialog content */}
</div>Props for the dialog title element, used for ARIA labeling.
/**
* Get props for dialog title element
* @returns Element props for title
*/
getTitleProps(): TitleProps;
interface TitleProps {
/** Element ID */
id: string;
/** Text direction */
dir: "ltr" | "rtl";
}Usage Example:
<h2 {...api.getTitleProps()}>
My Dialog Title
</h2>Props for the dialog description element, used for ARIA descriptions.
/**
* Get props for dialog description element
* @returns Element props for description
*/
getDescriptionProps(): DescriptionProps;
interface DescriptionProps {
/** Element ID */
id: string;
/** Text direction */
dir: "ltr" | "rtl";
}Usage Example:
<p {...api.getDescriptionProps()}>
This dialog contains important information.
</p>Props for the button that closes the dialog, with proper event handling.
/**
* Get props for dialog close button
* @returns Button props with close handling
*/
getCloseTriggerProps(): CloseButtonProps;
interface CloseButtonProps {
/** Element ID */
id: string;
/** Button type */
type: "button";
/** Text direction */
dir: "ltr" | "rtl";
/** Click handler with event propagation control */
onClick: (event: MouseEvent) => void;
}Usage Example:
<button {...api.getCloseTriggerProps()}>
✕ Close
</button>import { machine, connect } from "@zag-js/dialog";
import { normalizeProps } from "@zag-js/react"; // or your framework
function MyDialog() {
const [state, send] = useMachine(
machine({
id: "my-dialog",
modal: true,
closeOnEscape: true,
onOpenChange: (details) => {
console.log("Dialog open state:", details.open);
}
})
);
const api = connect(state, normalizeProps);
return (
<div>
{/* Trigger */}
<button {...api.getTriggerProps()}>
Open Dialog
</button>
{/* Dialog Portal */}
{api.open && (
<div {...api.getPositionerProps()}>
{/* Backdrop */}
<div {...api.getBackdropProps()} />
{/* Content */}
<div {...api.getContentProps()}>
<h2 {...api.getTitleProps()}>
Confirmation
</h2>
<p {...api.getDescriptionProps()}>
Are you sure you want to delete this item?
</p>
<div>
<button {...api.getCloseTriggerProps()}>
Cancel
</button>
<button onClick={() => {
// Perform action
api.setOpen(false);
}}>
Delete
</button>
</div>
</div>
</div>
)}
</div>
);
}Install with Tessl CLI
npx tessl i tessl/npm-zag-js--dialog