Core logic for the dialog widget implemented as a state machine
npx @tessl/cli install tessl/npm-zag-js--dialog@1.22.00
# Zag.js Dialog
1
2
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.
3
4
## Package Information
5
6
- **Package Name**: @zag-js/dialog
7
- **Package Type**: npm
8
- **Language**: TypeScript
9
- **Installation**: `npm install @zag-js/dialog`
10
11
## Core Imports
12
13
```typescript
14
import { machine, connect, anatomy, props, splitProps } from "@zag-js/dialog";
15
import type { Api, Props, Service, ElementIds, OpenChangeDetails } from "@zag-js/dialog";
16
import type { FocusOutsideEvent, InteractOutsideEvent, PointerDownOutsideEvent } from "@zag-js/dialog";
17
```
18
19
For CommonJS:
20
21
```javascript
22
const { machine, connect, anatomy, props, splitProps } = require("@zag-js/dialog");
23
```
24
25
## Basic Usage
26
27
```typescript
28
import { machine, connect } from "@zag-js/dialog";
29
import { normalizeProps } from "@zag-js/react"; // or your framework adapter
30
31
function MyDialog() {
32
// Create machine instance
33
const [state, send] = useMachine(
34
machine({
35
id: "dialog-1",
36
onOpenChange: (details) => {
37
console.log("Dialog open:", details.open);
38
},
39
})
40
);
41
42
// Connect machine to UI
43
const api = connect(state, normalizeProps);
44
45
return (
46
<div>
47
<button {...api.getTriggerProps()}>Open Dialog</button>
48
49
{api.open && (
50
<div {...api.getPositionerProps()}>
51
<div {...api.getBackdropProps()} />
52
<div {...api.getContentProps()}>
53
<h2 {...api.getTitleProps()}>Dialog Title</h2>
54
<p {...api.getDescriptionProps()}>Dialog content goes here</p>
55
<button {...api.getCloseTriggerProps()}>Close</button>
56
</div>
57
</div>
58
)}
59
</div>
60
);
61
}
62
```
63
64
## Architecture
65
66
Zag.js Dialog is built around several key components:
67
68
- **State Machine**: Core logic implemented as a finite state machine with states (open/closed), events, effects, and actions
69
- **Connect API**: Framework-agnostic connection layer that provides prop functions for UI elements
70
- **Anatomy System**: Structured component parts with consistent naming and behavior
71
- **Accessibility Layer**: Built-in ARIA roles, keyboard interactions, and focus management
72
- **Framework Adapters**: Integration with React, Vue, Solid, Svelte through separate adapter packages
73
74
## Capabilities
75
76
### Dialog State Machine
77
78
Core state machine that manages dialog open/closed states, handles events, and orchestrates all dialog behaviors including accessibility features.
79
80
```typescript { .api }
81
function machine(props: Props): Machine;
82
83
interface Machine extends StateMachine<DialogSchema> {
84
state: "open" | "closed";
85
send: (event: MachineEvent) => void;
86
}
87
88
interface MachineEvent {
89
type: "OPEN" | "CLOSE" | "TOGGLE" | "CONTROLLED.OPEN" | "CONTROLLED.CLOSE";
90
}
91
```
92
93
[Dialog State Machine](./machine.md)
94
95
### API Connection
96
97
Connection layer that transforms state machine into framework-agnostic prop functions for UI elements, providing complete accessibility and interaction handling.
98
99
```typescript { .api }
100
function connect<T extends PropTypes>(
101
service: Service<DialogSchema>,
102
normalize: NormalizeProps<T>
103
): Api<T>;
104
105
interface Api<T extends PropTypes> {
106
open: boolean;
107
setOpen: (open: boolean) => void;
108
getTriggerProps: () => T["button"];
109
getBackdropProps: () => T["element"];
110
getPositionerProps: () => T["element"];
111
getContentProps: () => T["element"];
112
getTitleProps: () => T["element"];
113
getDescriptionProps: () => T["element"];
114
getCloseTriggerProps: () => T["button"];
115
}
116
```
117
118
[API Connection Methods](./connect.md)
119
120
### Component Anatomy
121
122
Anatomical structure defining the dialog's component parts with consistent naming and CSS class generation.
123
124
```typescript { .api }
125
interface Anatomy {
126
trigger: AnatomyPart;
127
backdrop: AnatomyPart;
128
positioner: AnatomyPart;
129
content: AnatomyPart;
130
title: AnatomyPart;
131
description: AnatomyPart;
132
closeTrigger: AnatomyPart;
133
}
134
135
const anatomy: Anatomy;
136
```
137
138
### Event Types
139
140
Event types for dismissible interactions that can occur outside the dialog.
141
142
```typescript { .api }
143
type FocusOutsideEvent = CustomEvent & {
144
target: Element;
145
preventDefault: () => void;
146
};
147
148
type InteractOutsideEvent = CustomEvent & {
149
target: Element;
150
preventDefault: () => void;
151
};
152
153
type PointerDownOutsideEvent = CustomEvent & {
154
target: Element;
155
preventDefault: () => void;
156
};
157
```
158
159
### Props Utilities
160
161
Utility functions for working with dialog props in framework integrations.
162
163
```typescript { .api }
164
/**
165
* Props utility for type-safe prop definitions
166
*/
167
const props: PropsDefinition<Props>;
168
169
/**
170
* Split props utility for separating dialog props from other props
171
* @param allProps - Object containing all props
172
* @returns Tuple with [dialogProps, otherProps]
173
*/
174
const splitProps: <T extends Partial<Props>>(
175
allProps: T & Record<string, any>
176
) => [T, Record<string, any>];
177
178
interface PropsDefinition<T> {
179
/** Array of prop keys for type validation */
180
keys: (keyof T)[];
181
/** Type-safe prop object */
182
object: T;
183
}
184
```
185
186
## Types
187
188
### Configuration Props
189
190
```typescript { .api }
191
interface Props {
192
// Element IDs
193
ids?: ElementIds;
194
195
// State Management
196
open?: boolean;
197
defaultOpen?: boolean;
198
onOpenChange?: (details: OpenChangeDetails) => void;
199
200
// Behavior Configuration
201
modal?: boolean;
202
trapFocus?: boolean;
203
preventScroll?: boolean;
204
restoreFocus?: boolean;
205
closeOnInteractOutside?: boolean;
206
closeOnEscape?: boolean;
207
208
// Accessibility
209
role?: "dialog" | "alertdialog";
210
"aria-label"?: string;
211
212
// Focus Management
213
initialFocusEl?: () => MaybeElement;
214
finalFocusEl?: () => MaybeElement;
215
216
// Event Handlers
217
onInteractOutside?: (event: InteractOutsideEvent) => void;
218
onFocusOutside?: (event: FocusOutsideEvent) => void;
219
onPointerDownOutside?: (event: PointerDownOutsideEvent) => void;
220
onEscapeKeyDown?: (event: KeyboardEvent) => void;
221
onRequestDismiss?: () => void;
222
223
// Other
224
dir?: "ltr" | "rtl";
225
id?: string;
226
getRootNode?: () => Node | ShadowRoot | Document;
227
persistentElements?: () => Element[];
228
}
229
230
interface ElementIds {
231
trigger?: string;
232
positioner?: string;
233
backdrop?: string;
234
content?: string;
235
closeTrigger?: string;
236
title?: string;
237
description?: string;
238
}
239
240
interface OpenChangeDetails {
241
open: boolean;
242
}
243
244
type MaybeElement = Element | null | undefined;
245
```
246
247
### Service Types
248
249
```typescript { .api }
250
interface Service extends StateMachineService<DialogSchema> {
251
state: State;
252
send: (event: MachineEvent) => void;
253
context: Context;
254
prop: (key: string) => any;
255
scope: Scope;
256
}
257
258
interface DialogSchema {
259
props: Props;
260
state: "open" | "closed";
261
context: {
262
rendered: { title: boolean; description: boolean };
263
};
264
guard: "isOpenControlled";
265
effect: "trackDismissableElement" | "preventScroll" | "trapFocus" | "hideContentBelow";
266
action: "checkRenderedElements" | "syncZIndex" | "invokeOnClose" | "invokeOnOpen" | "toggleVisibility";
267
event: MachineEvent;
268
}
269
```