Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/bold-horses-rhyme.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
---
---
44 changes: 23 additions & 21 deletions packages/swingset/src/stories/dialog.component.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,13 +7,27 @@ primitives, composed with Mosaic slot recipes. It flattens the required nesting
Backdrop, Viewport, Popup) into a single component and exposes a `close` callback through a
render-prop children pattern.

## Example
## Playground

<Story
<Preview
name='Default'
storyModule={DialogStories}
/>

## Props

<PropTable
meta={DialogStories.meta}
extra={[
{ name: 'trigger', type: '(props: HTMLAttributes<HTMLElement>) => ReactElement' },
{ name: 'children', type: 'ReactNode | ((ctx: { close: () => void }) => ReactNode)' },
{ name: 'open', type: 'boolean' },
{ name: 'defaultOpen', type: 'boolean', default: 'false' },
{ name: 'onOpenChange', type: '(open: boolean) => void' },
{ name: 'modal', type: 'boolean', default: 'true' },
]}
Comment on lines +19 to +28

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor | ⚑ Quick win

Provide defaults for every prop in the generated props table input.

In extra, several props (trigger, children, open, onOpenChange) do not declare a default. Please set explicit defaults (typically β€”) so the rendered table stays compliant and unambiguous.

As per coding guidelines: β€œIn all props tables, document the default value for every prop in a dedicated Default column … use β€” when there is no default.”

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/swingset/src/stories/dialog.component.mdx` around lines 19 - 28, The
PropTable component in the dialog.component.mdx file has an extra array with
prop definitions where some props are missing the default field. Add a default
property to each prop object in the extra array that currently lacks one
(trigger, children, open, and onOpenChange). For props without a meaningful
default value, use "β€”" as the default to comply with the props table
documentation standards. Ensure all prop objects in the extra array now include
an explicit default field.

Source: Coding guidelines

/>

## Usage
Comment on lines +10 to 31

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚑ Quick win

Add the required Examples section for Archetype A docs.

This page currently has Playground β†’ Props β†’ Usage but omits Examples, which is required for Archetype A MDX docs.

As per coding guidelines: β€œFor Archetype A (simple CVA) MDX files, required sections in order: Playground, Props, Usage, then Examples. Never reorder or omit these sections.”

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/swingset/src/stories/dialog.component.mdx` around lines 10 - 31, The
dialog.component.mdx file is missing the required Examples section which is
mandatory for Archetype A documentation. The file currently contains Playground,
Props, and Usage sections, but according to guidelines, the Examples section
must be included after Usage. Add an Examples section after the Usage section
heading with relevant code examples demonstrating how to use the Dialog
component in different scenarios.

Source: Coding guidelines


```tsx
Expand DownExpand Up@@ -63,18 +77,6 @@ const [open, setOpen] = useState(false);
</Dialog>
```

## Props

| Prop | Type | Default | Description |
| -------------- | ---------------------------------------------------------- | ------- | ------------------------------------------------- |
| `trigger` | `(props: HTMLAttributes<HTMLElement>) => ReactElement` | β€” | Render prop for the trigger element |
| `children` | `ReactNode \| ((ctx: { close: () => void }) => ReactNode)` | β€” | Dialog content; use render prop to access `close` |
| `size` | `'md' \| 'lg'` | `'md'` | Controls the popup width |
| `open` | `boolean` | β€” | Controlled open state |
| `defaultOpen` | `boolean` | `false` | Initial open state (uncontrolled) |
| `onOpenChange` | `(open: boolean) => void` | β€” | Called when the open state changes |
| `modal` | `boolean` | `true` | Trap focus and make the rest of the page inert |

## Sub-parts

| Part | Slot | Description |
Expand All@@ -97,10 +99,10 @@ The Mosaic dialog exposes the following slots that can be styled via `appearance
Override per slot through `appearance.elements` β€” e.g. `{ 'dialog-popup': { borderRadius: 24 } }`.
State attributes from the headless layer are also available for CSS targeting:

| Attribute | Applies To | Description |
| ------------------------ | --------------------------- | --------------------------------- |
| `data-cl-slot` | All parts | Part identifier |
| `data-cl-open` | Trigger, Backdrop, Viewport | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Viewport | Present when closed (during exit) |
| `data-cl-starting-style` | Backdrop, Viewport, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Viewport, Popup | Present during the exit animation |
| Attribute | Applies To | Description |
| ------------------------ | ---------------------------------- | --------------------------------- |
| `data-cl-slot` | All parts | Part identifier |
| `data-cl-open` | Trigger, Backdrop, Viewport, Popup | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Viewport, Popup | Present when closed (during exit) |
| `data-cl-starting-style` | Backdrop, Viewport, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Viewport, Popup | Present during the exit animation |
84 changes: 47 additions & 37 deletions packages/swingset/src/stories/dialog.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,9 +4,9 @@ import * as DialogStories from './dialog.stories';

A modal window that overlays the page and traps focus until dismissed, from
`@clerk/headless`. It is a **headless** primitive: it supplies open state, portalling, an
Comment on lines 5 to 6

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor | ⚑ Quick win

Clarify focus-trap wording as modal-only behavior.

The intro and Dialog.Popup description read as unconditional focus trapping, but this page also documents modal={false} where focus is not trapped. Please qualify these lines as β€œin modal mode” (or β€œby default”).

Also applies to: 69-69

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/swingset/src/stories/dialog.mdx` around lines 5 - 6, The
documentation in the Dialog component description incorrectly presents focus
trapping as unconditional behavior, but the component supports a modal={false}
option where focus is not trapped. Locate the introductory description around
line 5-6 that states the component "traps focus until dismissed" and the
Dialog.Popup description around line 69, then add qualifying language such as
"in modal mode" or "by default" to both locations to clarify that focus trapping
only occurs when the modal prop is enabled. This ensures the documentation
accurately reflects that focus trapping is conditional behavior rather than a
default always-on feature.

overlay with optional scroll lock, focus management, dismissal (outside press / Escape),
and ARIA wiring, but ships **no styles** β€” you bring your own CSS by targeting the
`data-cl-*` attributes each part emits.
overlay surface, a centering viewport with optional body scroll lock, focus management,
dismissal (outside press / Escape), and ARIA wiring, but ships **no styles** β€” you bring
your own CSS by targeting the `data-cl-*` state attributes each part emits.

## Example

Expand All@@ -26,13 +26,14 @@ import { Dialog } from '@clerk/headless/dialog';
<Dialog.Root>
<Dialog.Trigger>Open</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Backdrop>
<Dialog.Backdrop />
<Dialog.Viewport>
<Dialog.Popup>
<Dialog.Title>Confirm action</Dialog.Title>
<Dialog.Description>Are you sure you want to proceed?</Dialog.Description>
<Dialog.Close>Cancel</Dialog.Close>
</Dialog.Popup>
</Dialog.Backdrop>
</Dialog.Viewport>
</Dialog.Portal>
</Dialog.Root>;
```
Expand All@@ -50,23 +51,30 @@ const [open, setOpen] = useState(false);
</Dialog.Root>;
```

### Non-modal (page stays interactive)

```tsx
<Dialog.Root modal={false}>{/* focus is not trapped and the rest of the page stays interactive */}</Dialog.Root>
```

## Parts

| Part | Default Element | Description |
| -------------------- | --------------- | --------------------------------------------------------------- |
| `Dialog.Root` | none (context) | Owns open state, ARIA ids, and the transition lifecycle |
| `Dialog.Trigger` | `<button>` | Toggles the dialog open on click |
| `Dialog.Portal` | none (portal) | Portals its children; renders nothing until mounted |
| `Dialog.Backdrop` | `<div>` | Overlay; hosts the fixed backdrop and optional body scroll lock |
| `Dialog.Popup` | `<div>` | The dialog content container (`role="dialog"`, focus-trapped) |
| `Dialog.Title` | `<h2>` | Heading; wired to the popup's `aria-labelledby` |
| `Dialog.Description` | `<p>` | Description; wired to the popup's `aria-describedby` |
| `Dialog.Close` | `<button>` | Closes the dialog on click |
| Part | Default Element | Description |
| -------------------- | --------------- | ------------------------------------------------------------------- |
| `Dialog.Root` | none (context) | Owns open state, ARIA ids, modal mode, and the transition lifecycle |
| `Dialog.Trigger` | `<button>` | Toggles the dialog open on click |
| `Dialog.Portal` | none (portal) | Portals its children; renders nothing until mounted |
| `Dialog.Backdrop` | `<div>` | Semi-transparent overlay surface behind the popup |
| `Dialog.Viewport` | `<div>` | Fixed centering container; owns body scroll lock |
| `Dialog.Popup` | `<div>` | The dialog content container (`role="dialog"`, focus-trapped) |
| `Dialog.Title` | `<h2>` | Heading; wired to the popup's `aria-labelledby` |
| `Dialog.Description` | `<p>` | Description; wired to the popup's `aria-describedby` |
| `Dialog.Close` | `<button>` | Closes the dialog on click |

All rendered parts accept a `render` prop for polymorphic rendering and standard HTML
attributes for their default element. `Dialog.Title` and `Dialog.Description` manage their
own `id`. `Dialog.Portal` is optional β€” `Dialog.Backdrop` may be nested directly under
`Dialog.Root`.
attributes for their default element. Compound parts throw if used outside `Dialog.Root`.
`Dialog.Title` and `Dialog.Description` manage their own `id`. `Dialog.Portal` is optional;
for centered, scroll-locked modal behavior nest `Dialog.Popup` inside `Dialog.Viewport`.

## Props

Expand All@@ -81,41 +89,43 @@ own `id`. `Dialog.Portal` is optional β€” `Dialog.Backdrop` may be nested direct

### `Dialog.Portal`

| Prop | Type | Default | Description |
| ----------------- | ---------------------------------------------------------------- | -------------------------- | ---------------------------------------------------------------------------------------------------- |
| <code>root</code> | <code>HTMLElement \| null \| RefObject&lt;HTMLElement&gt;</code> | <code>document.body</code> | Container to portal into; non-null switches the backdrop to "scoped" mode (no overlay / scroll lock) |
| Prop | Type | Default | Description |
| ----------------- | ---------------------------------------------------------------- | -------------------------- | -------------------------------- |
| <code>root</code> | <code>HTMLElement \| null \| RefObject&lt;HTMLElement&gt;</code> | <code>document.body</code> | Container element to portal into |

### `Dialog.Backdrop`
### `Dialog.Viewport`

| Prop | Type | Default | Description |
| ----------------------- | -------------------- | ----------------- | ----------------------------------------- |
| <code>lockScroll</code> | <code>boolean</code> | <code>true</code> | Lock body scroll while the dialog is open |

`Dialog.Trigger`, `Dialog.Popup`, `Dialog.Title`, `Dialog.Description`, and `Dialog.Close`
take no additional props beyond standard HTML attributes for their default element.
`Dialog.Trigger`, `Dialog.Backdrop`, `Dialog.Popup`, `Dialog.Title`, `Dialog.Description`,
and `Dialog.Close` take no additional props beyond standard HTML attributes for their
default element.

## Styling

Each part emits `data-cl-*` attributes you can target with any CSS solution:
The headless parts don't emit `data-cl-slot` β€” slot identity is applied by the styled
(Mosaic) layer. Target a part with your own class (or `render` prop) and combine it with
the `data-cl-*` state attributes each part emits:

| Attribute | Applies To | Description |
| ------------------------ | ------------------------ | ------------------------------------------------- |
| `data-cl-slot` | All parts | Part identifier |
| `data-cl-open` | Trigger, Backdrop, Popup | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Popup | Present when closed (still mounted while exiting) |
| `data-cl-starting-style` | Backdrop, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Popup | Present during the exit animation |
| Attribute | Applies To | Description |
| ------------------------ | ---------------------------------- | ------------------------------------------------- |
| `data-cl-open` | Trigger, Backdrop, Viewport, Popup | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Viewport, Popup | Present when closed (still mounted while exiting) |
| `data-cl-starting-style` | Backdrop, Viewport, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Viewport, Popup | Present during the exit animation |

The backdropand popup stay mounted through the exit animation, so enter/exit transitions
are CSS-driven:
The backdrop, viewport, and popup stay mounted through the exit animation, so enter/exit
transitions are CSS-driven:

```css
[data-cl-slot='dialog-popup'] {
.dialog-popup {
opacity: 1;
transition: opacity 150ms ease;
}
[data-cl-slot='dialog-popup'][data-cl-starting-style],
[data-cl-slot='dialog-popup'][data-cl-ending-style] {
.dialog-popup[data-cl-starting-style],
.dialog-popup[data-cl-ending-style] {
opacity: 0;
}
```
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/bold-horses-rhyme.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
---
---
44 changes: 23 additions & 21 deletions packages/swingset/src/stories/dialog.component.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,13 +7,27 @@ primitives, composed with Mosaic slot recipes. It flattens the required nesting
Backdrop, Viewport, Popup) into a single component and exposes a `close` callback through a
render-prop children pattern.

## Example
## Playground

<Story
<Preview
name='Default'
storyModule={DialogStories}
/>

## Props

<PropTable
meta={DialogStories.meta}
extra={[
{ name: 'trigger', type: '(props: HTMLAttributes<HTMLElement>) => ReactElement' },
{ name: 'children', type: 'ReactNode | ((ctx: { close: () => void }) => ReactNode)' },
{ name: 'open', type: 'boolean' },
{ name: 'defaultOpen', type: 'boolean', default: 'false' },
{ name: 'onOpenChange', type: '(open: boolean) => void' },
{ name: 'modal', type: 'boolean', default: 'true' },
]}
Comment on lines +19 to +28

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor | ⚑ Quick win

Provide defaults for every prop in the generated props table input.

In extra, several props (trigger, children, open, onOpenChange) do not declare a default. Please set explicit defaults (typically β€”) so the rendered table stays compliant and unambiguous.

As per coding guidelines: β€œIn all props tables, document the default value for every prop in a dedicated Default column … use β€” when there is no default.”

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/swingset/src/stories/dialog.component.mdx` around lines 19 - 28, The
PropTable component in the dialog.component.mdx file has an extra array with
prop definitions where some props are missing the default field. Add a default
property to each prop object in the extra array that currently lacks one
(trigger, children, open, and onOpenChange). For props without a meaningful
default value, use "β€”" as the default to comply with the props table
documentation standards. Ensure all prop objects in the extra array now include
an explicit default field.

Source: Coding guidelines

/>

## Usage
Comment on lines +10 to 31

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚑ Quick win

Add the required Examples section for Archetype A docs.

This page currently has Playground β†’ Props β†’ Usage but omits Examples, which is required for Archetype A MDX docs.

As per coding guidelines: β€œFor Archetype A (simple CVA) MDX files, required sections in order: Playground, Props, Usage, then Examples. Never reorder or omit these sections.”

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/swingset/src/stories/dialog.component.mdx` around lines 10 - 31, The
dialog.component.mdx file is missing the required Examples section which is
mandatory for Archetype A documentation. The file currently contains Playground,
Props, and Usage sections, but according to guidelines, the Examples section
must be included after Usage. Add an Examples section after the Usage section
heading with relevant code examples demonstrating how to use the Dialog
component in different scenarios.

Source: Coding guidelines


```tsx
Expand DownExpand Up@@ -63,18 +77,6 @@ const [open, setOpen] = useState(false);
</Dialog>
```

## Props

| Prop | Type | Default | Description |
| -------------- | ---------------------------------------------------------- | ------- | ------------------------------------------------- |
| `trigger` | `(props: HTMLAttributes<HTMLElement>) => ReactElement` | β€” | Render prop for the trigger element |
| `children` | `ReactNode \| ((ctx: { close: () => void }) => ReactNode)` | β€” | Dialog content; use render prop to access `close` |
| `size` | `'md' \| 'lg'` | `'md'` | Controls the popup width |
| `open` | `boolean` | β€” | Controlled open state |
| `defaultOpen` | `boolean` | `false` | Initial open state (uncontrolled) |
| `onOpenChange` | `(open: boolean) => void` | β€” | Called when the open state changes |
| `modal` | `boolean` | `true` | Trap focus and make the rest of the page inert |

## Sub-parts

| Part | Slot | Description |
Expand All@@ -97,10 +99,10 @@ The Mosaic dialog exposes the following slots that can be styled via `appearance
Override per slot through `appearance.elements` β€” e.g. `{ 'dialog-popup': { borderRadius: 24 } }`.
State attributes from the headless layer are also available for CSS targeting:

| Attribute | Applies To | Description |
| ------------------------ | --------------------------- | --------------------------------- |
| `data-cl-slot` | All parts | Part identifier |
| `data-cl-open` | Trigger, Backdrop, Viewport | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Viewport | Present when closed (during exit) |
| `data-cl-starting-style` | Backdrop, Viewport, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Viewport, Popup | Present during the exit animation |
| Attribute | Applies To | Description |
| ------------------------ | ---------------------------------- | --------------------------------- |
| `data-cl-slot` | All parts | Part identifier |
| `data-cl-open` | Trigger, Backdrop, Viewport, Popup | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Viewport, Popup | Present when closed (during exit) |
| `data-cl-starting-style` | Backdrop, Viewport, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Viewport, Popup | Present during the exit animation |
84 changes: 47 additions & 37 deletions packages/swingset/src/stories/dialog.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,9 +4,9 @@ import * as DialogStories from './dialog.stories';

A modal window that overlays the page and traps focus until dismissed, from
`@clerk/headless`. It is a **headless** primitive: it supplies open state, portalling, an
Comment on lines 5 to 6

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor | ⚑ Quick win

Clarify focus-trap wording as modal-only behavior.

The intro and Dialog.Popup description read as unconditional focus trapping, but this page also documents modal={false} where focus is not trapped. Please qualify these lines as β€œin modal mode” (or β€œby default”).

Also applies to: 69-69

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/swingset/src/stories/dialog.mdx` around lines 5 - 6, The
documentation in the Dialog component description incorrectly presents focus
trapping as unconditional behavior, but the component supports a modal={false}
option where focus is not trapped. Locate the introductory description around
line 5-6 that states the component "traps focus until dismissed" and the
Dialog.Popup description around line 69, then add qualifying language such as
"in modal mode" or "by default" to both locations to clarify that focus trapping
only occurs when the modal prop is enabled. This ensures the documentation
accurately reflects that focus trapping is conditional behavior rather than a
default always-on feature.

overlay with optional scroll lock, focus management, dismissal (outside press / Escape),
and ARIA wiring, but ships **no styles** β€” you bring your own CSS by targeting the
`data-cl-*` attributes each part emits.
overlay surface, a centering viewport with optional body scroll lock, focus management,
dismissal (outside press / Escape), and ARIA wiring, but ships **no styles** β€” you bring
your own CSS by targeting the `data-cl-*` state attributes each part emits.

## Example

Expand All@@ -26,13 +26,14 @@ import { Dialog } from '@clerk/headless/dialog';
<Dialog.Root>
<Dialog.Trigger>Open</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Backdrop>
<Dialog.Backdrop />
<Dialog.Viewport>
<Dialog.Popup>
<Dialog.Title>Confirm action</Dialog.Title>
<Dialog.Description>Are you sure you want to proceed?</Dialog.Description>
<Dialog.Close>Cancel</Dialog.Close>
</Dialog.Popup>
</Dialog.Backdrop>
</Dialog.Viewport>
</Dialog.Portal>
</Dialog.Root>;
```
Expand All@@ -50,23 +51,30 @@ const [open, setOpen] = useState(false);
</Dialog.Root>;
```

### Non-modal (page stays interactive)

```tsx
<Dialog.Root modal={false}>{/* focus is not trapped and the rest of the page stays interactive */}</Dialog.Root>
```

## Parts

| Part | Default Element | Description |
| -------------------- | --------------- | --------------------------------------------------------------- |
| `Dialog.Root` | none (context) | Owns open state, ARIA ids, and the transition lifecycle |
| `Dialog.Trigger` | `<button>` | Toggles the dialog open on click |
| `Dialog.Portal` | none (portal) | Portals its children; renders nothing until mounted |
| `Dialog.Backdrop` | `<div>` | Overlay; hosts the fixed backdrop and optional body scroll lock |
| `Dialog.Popup` | `<div>` | The dialog content container (`role="dialog"`, focus-trapped) |
| `Dialog.Title` | `<h2>` | Heading; wired to the popup's `aria-labelledby` |
| `Dialog.Description` | `<p>` | Description; wired to the popup's `aria-describedby` |
| `Dialog.Close` | `<button>` | Closes the dialog on click |
| Part | Default Element | Description |
| -------------------- | --------------- | ------------------------------------------------------------------- |
| `Dialog.Root` | none (context) | Owns open state, ARIA ids, modal mode, and the transition lifecycle |
| `Dialog.Trigger` | `<button>` | Toggles the dialog open on click |
| `Dialog.Portal` | none (portal) | Portals its children; renders nothing until mounted |
| `Dialog.Backdrop` | `<div>` | Semi-transparent overlay surface behind the popup |
| `Dialog.Viewport` | `<div>` | Fixed centering container; owns body scroll lock |
| `Dialog.Popup` | `<div>` | The dialog content container (`role="dialog"`, focus-trapped) |
| `Dialog.Title` | `<h2>` | Heading; wired to the popup's `aria-labelledby` |
| `Dialog.Description` | `<p>` | Description; wired to the popup's `aria-describedby` |
| `Dialog.Close` | `<button>` | Closes the dialog on click |

All rendered parts accept a `render` prop for polymorphic rendering and standard HTML
attributes for their default element. `Dialog.Title` and `Dialog.Description` manage their
own `id`. `Dialog.Portal` is optional β€” `Dialog.Backdrop` may be nested directly under
`Dialog.Root`.
attributes for their default element. Compound parts throw if used outside `Dialog.Root`.
`Dialog.Title` and `Dialog.Description` manage their own `id`. `Dialog.Portal` is optional;
for centered, scroll-locked modal behavior nest `Dialog.Popup` inside `Dialog.Viewport`.

## Props

Expand All@@ -81,41 +89,43 @@ own `id`. `Dialog.Portal` is optional β€” `Dialog.Backdrop` may be nested direct

### `Dialog.Portal`

| Prop | Type | Default | Description |
| ----------------- | ---------------------------------------------------------------- | -------------------------- | ---------------------------------------------------------------------------------------------------- |
| <code>root</code> | <code>HTMLElement \| null \| RefObject&lt;HTMLElement&gt;</code> | <code>document.body</code> | Container to portal into; non-null switches the backdrop to "scoped" mode (no overlay / scroll lock) |
| Prop | Type | Default | Description |
| ----------------- | ---------------------------------------------------------------- | -------------------------- | -------------------------------- |
| <code>root</code> | <code>HTMLElement \| null \| RefObject&lt;HTMLElement&gt;</code> | <code>document.body</code> | Container element to portal into |

### `Dialog.Backdrop`
### `Dialog.Viewport`

| Prop | Type | Default | Description |
| ----------------------- | -------------------- | ----------------- | ----------------------------------------- |
| <code>lockScroll</code> | <code>boolean</code> | <code>true</code> | Lock body scroll while the dialog is open |

`Dialog.Trigger`, `Dialog.Popup`, `Dialog.Title`, `Dialog.Description`, and `Dialog.Close`
take no additional props beyond standard HTML attributes for their default element.
`Dialog.Trigger`, `Dialog.Backdrop`, `Dialog.Popup`, `Dialog.Title`, `Dialog.Description`,
and `Dialog.Close` take no additional props beyond standard HTML attributes for their
default element.

## Styling

Each part emits `data-cl-*` attributes you can target with any CSS solution:
The headless parts don't emit `data-cl-slot` β€” slot identity is applied by the styled
(Mosaic) layer. Target a part with your own class (or `render` prop) and combine it with
the `data-cl-*` state attributes each part emits:

| Attribute | Applies To | Description |
| ------------------------ | ------------------------ | ------------------------------------------------- |
| `data-cl-slot` | All parts | Part identifier |
| `data-cl-open` | Trigger, Backdrop, Popup | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Popup | Present when closed (still mounted while exiting) |
| `data-cl-starting-style` | Backdrop, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Popup | Present during the exit animation |
| Attribute | Applies To | Description |
| ------------------------ | ---------------------------------- | ------------------------------------------------- |
| `data-cl-open` | Trigger, Backdrop, Viewport, Popup | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Viewport, Popup | Present when closed (still mounted while exiting) |
| `data-cl-starting-style` | Backdrop, Viewport, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Viewport, Popup | Present during the exit animation |

The backdropand popup stay mounted through the exit animation, so enter/exit transitions
are CSS-driven:
The backdrop, viewport, and popup stay mounted through the exit animation, so enter/exit
transitions are CSS-driven:

```css
[data-cl-slot='dialog-popup'] {
.dialog-popup {
opacity: 1;
transition: opacity 150ms ease;
}
[data-cl-slot='dialog-popup'][data-cl-starting-style],
[data-cl-slot='dialog-popup'][data-cl-ending-style] {
.dialog-popup[data-cl-starting-style],
.dialog-popup[data-cl-ending-style] {
opacity: 0;
}
```
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/bold-horses-rhyme.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
---
---
44 changes: 23 additions & 21 deletions packages/swingset/src/stories/dialog.component.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,13 +7,27 @@ primitives, composed with Mosaic slot recipes. It flattens the required nesting
Backdrop, Viewport, Popup) into a single component and exposes a `close` callback through a
render-prop children pattern.

## Example
## Playground

<Story
<Preview
name='Default'
storyModule={DialogStories}
/>

## Props

<PropTable
meta={DialogStories.meta}
extra={[
{ name: 'trigger', type: '(props: HTMLAttributes<HTMLElement>) => ReactElement' },
{ name: 'children', type: 'ReactNode | ((ctx: { close: () => void }) => ReactNode)' },
{ name: 'open', type: 'boolean' },
{ name: 'defaultOpen', type: 'boolean', default: 'false' },
{ name: 'onOpenChange', type: '(open: boolean) => void' },
{ name: 'modal', type: 'boolean', default: 'true' },
]}
Comment on lines +19 to +28

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor | ⚑ Quick win

Provide defaults for every prop in the generated props table input.

In extra, several props (trigger, children, open, onOpenChange) do not declare a default. Please set explicit defaults (typically β€”) so the rendered table stays compliant and unambiguous.

As per coding guidelines: β€œIn all props tables, document the default value for every prop in a dedicated Default column … use β€” when there is no default.”

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/swingset/src/stories/dialog.component.mdx` around lines 19 - 28, The
PropTable component in the dialog.component.mdx file has an extra array with
prop definitions where some props are missing the default field. Add a default
property to each prop object in the extra array that currently lacks one
(trigger, children, open, and onOpenChange). For props without a meaningful
default value, use "β€”" as the default to comply with the props table
documentation standards. Ensure all prop objects in the extra array now include
an explicit default field.

Source: Coding guidelines

/>

## Usage
Comment on lines +10 to 31

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚑ Quick win

Add the required Examples section for Archetype A docs.

This page currently has Playground β†’ Props β†’ Usage but omits Examples, which is required for Archetype A MDX docs.

As per coding guidelines: β€œFor Archetype A (simple CVA) MDX files, required sections in order: Playground, Props, Usage, then Examples. Never reorder or omit these sections.”

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/swingset/src/stories/dialog.component.mdx` around lines 10 - 31, The
dialog.component.mdx file is missing the required Examples section which is
mandatory for Archetype A documentation. The file currently contains Playground,
Props, and Usage sections, but according to guidelines, the Examples section
must be included after Usage. Add an Examples section after the Usage section
heading with relevant code examples demonstrating how to use the Dialog
component in different scenarios.

Source: Coding guidelines


```tsx
Expand DownExpand Up@@ -63,18 +77,6 @@ const [open, setOpen] = useState(false);
</Dialog>
```

## Props

| Prop | Type | Default | Description |
| -------------- | ---------------------------------------------------------- | ------- | ------------------------------------------------- |
| `trigger` | `(props: HTMLAttributes<HTMLElement>) => ReactElement` | β€” | Render prop for the trigger element |
| `children` | `ReactNode \| ((ctx: { close: () => void }) => ReactNode)` | β€” | Dialog content; use render prop to access `close` |
| `size` | `'md' \| 'lg'` | `'md'` | Controls the popup width |
| `open` | `boolean` | β€” | Controlled open state |
| `defaultOpen` | `boolean` | `false` | Initial open state (uncontrolled) |
| `onOpenChange` | `(open: boolean) => void` | β€” | Called when the open state changes |
| `modal` | `boolean` | `true` | Trap focus and make the rest of the page inert |

## Sub-parts

| Part | Slot | Description |
Expand All@@ -97,10 +99,10 @@ The Mosaic dialog exposes the following slots that can be styled via `appearance
Override per slot through `appearance.elements` β€” e.g. `{ 'dialog-popup': { borderRadius: 24 } }`.
State attributes from the headless layer are also available for CSS targeting:

| Attribute | Applies To | Description |
| ------------------------ | --------------------------- | --------------------------------- |
| `data-cl-slot` | All parts | Part identifier |
| `data-cl-open` | Trigger, Backdrop, Viewport | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Viewport | Present when closed (during exit) |
| `data-cl-starting-style` | Backdrop, Viewport, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Viewport, Popup | Present during the exit animation |
| Attribute | Applies To | Description |
| ------------------------ | ---------------------------------- | --------------------------------- |
| `data-cl-slot` | All parts | Part identifier |
| `data-cl-open` | Trigger, Backdrop, Viewport, Popup | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Viewport, Popup | Present when closed (during exit) |
| `data-cl-starting-style` | Backdrop, Viewport, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Viewport, Popup | Present during the exit animation |
84 changes: 47 additions & 37 deletions packages/swingset/src/stories/dialog.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,9 +4,9 @@ import * as DialogStories from './dialog.stories';

A modal window that overlays the page and traps focus until dismissed, from
`@clerk/headless`. It is a **headless** primitive: it supplies open state, portalling, an
Comment on lines 5 to 6

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor | ⚑ Quick win

Clarify focus-trap wording as modal-only behavior.

The intro and Dialog.Popup description read as unconditional focus trapping, but this page also documents modal={false} where focus is not trapped. Please qualify these lines as β€œin modal mode” (or β€œby default”).

Also applies to: 69-69

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/swingset/src/stories/dialog.mdx` around lines 5 - 6, The
documentation in the Dialog component description incorrectly presents focus
trapping as unconditional behavior, but the component supports a modal={false}
option where focus is not trapped. Locate the introductory description around
line 5-6 that states the component "traps focus until dismissed" and the
Dialog.Popup description around line 69, then add qualifying language such as
"in modal mode" or "by default" to both locations to clarify that focus trapping
only occurs when the modal prop is enabled. This ensures the documentation
accurately reflects that focus trapping is conditional behavior rather than a
default always-on feature.

overlay with optional scroll lock, focus management, dismissal (outside press / Escape),
and ARIA wiring, but ships **no styles** β€” you bring your own CSS by targeting the
`data-cl-*` attributes each part emits.
overlay surface, a centering viewport with optional body scroll lock, focus management,
dismissal (outside press / Escape), and ARIA wiring, but ships **no styles** β€” you bring
your own CSS by targeting the `data-cl-*` state attributes each part emits.

## Example

Expand All@@ -26,13 +26,14 @@ import { Dialog } from '@clerk/headless/dialog';
<Dialog.Root>
<Dialog.Trigger>Open</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Backdrop>
<Dialog.Backdrop />
<Dialog.Viewport>
<Dialog.Popup>
<Dialog.Title>Confirm action</Dialog.Title>
<Dialog.Description>Are you sure you want to proceed?</Dialog.Description>
<Dialog.Close>Cancel</Dialog.Close>
</Dialog.Popup>
</Dialog.Backdrop>
</Dialog.Viewport>
</Dialog.Portal>
</Dialog.Root>;
```
Expand All@@ -50,23 +51,30 @@ const [open, setOpen] = useState(false);
</Dialog.Root>;
```

### Non-modal (page stays interactive)

```tsx
<Dialog.Root modal={false}>{/* focus is not trapped and the rest of the page stays interactive */}</Dialog.Root>
```

## Parts

| Part | Default Element | Description |
| -------------------- | --------------- | --------------------------------------------------------------- |
| `Dialog.Root` | none (context) | Owns open state, ARIA ids, and the transition lifecycle |
| `Dialog.Trigger` | `<button>` | Toggles the dialog open on click |
| `Dialog.Portal` | none (portal) | Portals its children; renders nothing until mounted |
| `Dialog.Backdrop` | `<div>` | Overlay; hosts the fixed backdrop and optional body scroll lock |
| `Dialog.Popup` | `<div>` | The dialog content container (`role="dialog"`, focus-trapped) |
| `Dialog.Title` | `<h2>` | Heading; wired to the popup's `aria-labelledby` |
| `Dialog.Description` | `<p>` | Description; wired to the popup's `aria-describedby` |
| `Dialog.Close` | `<button>` | Closes the dialog on click |
| Part | Default Element | Description |
| -------------------- | --------------- | ------------------------------------------------------------------- |
| `Dialog.Root` | none (context) | Owns open state, ARIA ids, modal mode, and the transition lifecycle |
| `Dialog.Trigger` | `<button>` | Toggles the dialog open on click |
| `Dialog.Portal` | none (portal) | Portals its children; renders nothing until mounted |
| `Dialog.Backdrop` | `<div>` | Semi-transparent overlay surface behind the popup |
| `Dialog.Viewport` | `<div>` | Fixed centering container; owns body scroll lock |
| `Dialog.Popup` | `<div>` | The dialog content container (`role="dialog"`, focus-trapped) |
| `Dialog.Title` | `<h2>` | Heading; wired to the popup's `aria-labelledby` |
| `Dialog.Description` | `<p>` | Description; wired to the popup's `aria-describedby` |
| `Dialog.Close` | `<button>` | Closes the dialog on click |

All rendered parts accept a `render` prop for polymorphic rendering and standard HTML
attributes for their default element. `Dialog.Title` and `Dialog.Description` manage their
own `id`. `Dialog.Portal` is optional β€” `Dialog.Backdrop` may be nested directly under
`Dialog.Root`.
attributes for their default element. Compound parts throw if used outside `Dialog.Root`.
`Dialog.Title` and `Dialog.Description` manage their own `id`. `Dialog.Portal` is optional;
for centered, scroll-locked modal behavior nest `Dialog.Popup` inside `Dialog.Viewport`.

## Props

Expand All@@ -81,41 +89,43 @@ own `id`. `Dialog.Portal` is optional β€” `Dialog.Backdrop` may be nested direct

### `Dialog.Portal`

| Prop | Type | Default | Description |
| ----------------- | ---------------------------------------------------------------- | -------------------------- | ---------------------------------------------------------------------------------------------------- |
| <code>root</code> | <code>HTMLElement \| null \| RefObject&lt;HTMLElement&gt;</code> | <code>document.body</code> | Container to portal into; non-null switches the backdrop to "scoped" mode (no overlay / scroll lock) |
| Prop | Type | Default | Description |
| ----------------- | ---------------------------------------------------------------- | -------------------------- | -------------------------------- |
| <code>root</code> | <code>HTMLElement \| null \| RefObject&lt;HTMLElement&gt;</code> | <code>document.body</code> | Container element to portal into |

### `Dialog.Backdrop`
### `Dialog.Viewport`

| Prop | Type | Default | Description |
| ----------------------- | -------------------- | ----------------- | ----------------------------------------- |
| <code>lockScroll</code> | <code>boolean</code> | <code>true</code> | Lock body scroll while the dialog is open |

`Dialog.Trigger`, `Dialog.Popup`, `Dialog.Title`, `Dialog.Description`, and `Dialog.Close`
take no additional props beyond standard HTML attributes for their default element.
`Dialog.Trigger`, `Dialog.Backdrop`, `Dialog.Popup`, `Dialog.Title`, `Dialog.Description`,
and `Dialog.Close` take no additional props beyond standard HTML attributes for their
default element.

## Styling

Each part emits `data-cl-*` attributes you can target with any CSS solution:
The headless parts don't emit `data-cl-slot` β€” slot identity is applied by the styled
(Mosaic) layer. Target a part with your own class (or `render` prop) and combine it with
the `data-cl-*` state attributes each part emits:

| Attribute | Applies To | Description |
| ------------------------ | ------------------------ | ------------------------------------------------- |
| `data-cl-slot` | All parts | Part identifier |
| `data-cl-open` | Trigger, Backdrop, Popup | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Popup | Present when closed (still mounted while exiting) |
| `data-cl-starting-style` | Backdrop, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Popup | Present during the exit animation |
| Attribute | Applies To | Description |
| ------------------------ | ---------------------------------- | ------------------------------------------------- |
| `data-cl-open` | Trigger, Backdrop, Viewport, Popup | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Viewport, Popup | Present when closed (still mounted while exiting) |
| `data-cl-starting-style` | Backdrop, Viewport, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Viewport, Popup | Present during the exit animation |

The backdropand popup stay mounted through the exit animation, so enter/exit transitions
are CSS-driven:
The backdrop, viewport, and popup stay mounted through the exit animation, so enter/exit
transitions are CSS-driven:

```css
[data-cl-slot='dialog-popup'] {
.dialog-popup {
opacity: 1;
transition: opacity 150ms ease;
}
[data-cl-slot='dialog-popup'][data-cl-starting-style],
[data-cl-slot='dialog-popup'][data-cl-ending-style] {
.dialog-popup[data-cl-starting-style],
.dialog-popup[data-cl-ending-style] {
opacity: 0;
}
```
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/bold-horses-rhyme.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
---
---
44 changes: 23 additions & 21 deletions packages/swingset/src/stories/dialog.component.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,13 +7,27 @@ primitives, composed with Mosaic slot recipes. It flattens the required nesting
Backdrop, Viewport, Popup) into a single component and exposes a `close` callback through a
render-prop children pattern.

## Example
## Playground

<Story
<Preview
name='Default'
storyModule={DialogStories}
/>

## Props

<PropTable
meta={DialogStories.meta}
extra={[
{ name: 'trigger', type: '(props: HTMLAttributes<HTMLElement>) => ReactElement' },
{ name: 'children', type: 'ReactNode | ((ctx: { close: () => void }) => ReactNode)' },
{ name: 'open', type: 'boolean' },
{ name: 'defaultOpen', type: 'boolean', default: 'false' },
{ name: 'onOpenChange', type: '(open: boolean) => void' },
{ name: 'modal', type: 'boolean', default: 'true' },
]}
Comment on lines +19 to +28

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor | ⚑ Quick win

Provide defaults for every prop in the generated props table input.

In extra, several props (trigger, children, open, onOpenChange) do not declare a default. Please set explicit defaults (typically β€”) so the rendered table stays compliant and unambiguous.

As per coding guidelines: β€œIn all props tables, document the default value for every prop in a dedicated Default column … use β€” when there is no default.”

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/swingset/src/stories/dialog.component.mdx` around lines 19 - 28, The
PropTable component in the dialog.component.mdx file has an extra array with
prop definitions where some props are missing the default field. Add a default
property to each prop object in the extra array that currently lacks one
(trigger, children, open, and onOpenChange). For props without a meaningful
default value, use "β€”" as the default to comply with the props table
documentation standards. Ensure all prop objects in the extra array now include
an explicit default field.

Source: Coding guidelines

/>

## Usage
Comment on lines +10 to 31

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚑ Quick win

Add the required Examples section for Archetype A docs.

This page currently has Playground β†’ Props β†’ Usage but omits Examples, which is required for Archetype A MDX docs.

As per coding guidelines: β€œFor Archetype A (simple CVA) MDX files, required sections in order: Playground, Props, Usage, then Examples. Never reorder or omit these sections.”

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/swingset/src/stories/dialog.component.mdx` around lines 10 - 31, The
dialog.component.mdx file is missing the required Examples section which is
mandatory for Archetype A documentation. The file currently contains Playground,
Props, and Usage sections, but according to guidelines, the Examples section
must be included after Usage. Add an Examples section after the Usage section
heading with relevant code examples demonstrating how to use the Dialog
component in different scenarios.

Source: Coding guidelines


```tsx
Expand DownExpand Up@@ -63,18 +77,6 @@ const [open, setOpen] = useState(false);
</Dialog>
```

## Props

| Prop | Type | Default | Description |
| -------------- | ---------------------------------------------------------- | ------- | ------------------------------------------------- |
| `trigger` | `(props: HTMLAttributes<HTMLElement>) => ReactElement` | β€” | Render prop for the trigger element |
| `children` | `ReactNode \| ((ctx: { close: () => void }) => ReactNode)` | β€” | Dialog content; use render prop to access `close` |
| `size` | `'md' \| 'lg'` | `'md'` | Controls the popup width |
| `open` | `boolean` | β€” | Controlled open state |
| `defaultOpen` | `boolean` | `false` | Initial open state (uncontrolled) |
| `onOpenChange` | `(open: boolean) => void` | β€” | Called when the open state changes |
| `modal` | `boolean` | `true` | Trap focus and make the rest of the page inert |

## Sub-parts

| Part | Slot | Description |
Expand All@@ -97,10 +99,10 @@ The Mosaic dialog exposes the following slots that can be styled via `appearance
Override per slot through `appearance.elements` β€” e.g. `{ 'dialog-popup': { borderRadius: 24 } }`.
State attributes from the headless layer are also available for CSS targeting:

| Attribute | Applies To | Description |
| ------------------------ | --------------------------- | --------------------------------- |
| `data-cl-slot` | All parts | Part identifier |
| `data-cl-open` | Trigger, Backdrop, Viewport | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Viewport | Present when closed (during exit) |
| `data-cl-starting-style` | Backdrop, Viewport, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Viewport, Popup | Present during the exit animation |
| Attribute | Applies To | Description |
| ------------------------ | ---------------------------------- | --------------------------------- |
| `data-cl-slot` | All parts | Part identifier |
| `data-cl-open` | Trigger, Backdrop, Viewport, Popup | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Viewport, Popup | Present when closed (during exit) |
| `data-cl-starting-style` | Backdrop, Viewport, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Viewport, Popup | Present during the exit animation |
84 changes: 47 additions & 37 deletions packages/swingset/src/stories/dialog.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,9 +4,9 @@ import * as DialogStories from './dialog.stories';

A modal window that overlays the page and traps focus until dismissed, from
`@clerk/headless`. It is a **headless** primitive: it supplies open state, portalling, an
Comment on lines 5 to 6

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor | ⚑ Quick win

Clarify focus-trap wording as modal-only behavior.

The intro and Dialog.Popup description read as unconditional focus trapping, but this page also documents modal={false} where focus is not trapped. Please qualify these lines as β€œin modal mode” (or β€œby default”).

Also applies to: 69-69

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/swingset/src/stories/dialog.mdx` around lines 5 - 6, The
documentation in the Dialog component description incorrectly presents focus
trapping as unconditional behavior, but the component supports a modal={false}
option where focus is not trapped. Locate the introductory description around
line 5-6 that states the component "traps focus until dismissed" and the
Dialog.Popup description around line 69, then add qualifying language such as
"in modal mode" or "by default" to both locations to clarify that focus trapping
only occurs when the modal prop is enabled. This ensures the documentation
accurately reflects that focus trapping is conditional behavior rather than a
default always-on feature.

overlay with optional scroll lock, focus management, dismissal (outside press / Escape),
and ARIA wiring, but ships **no styles** β€” you bring your own CSS by targeting the
`data-cl-*` attributes each part emits.
overlay surface, a centering viewport with optional body scroll lock, focus management,
dismissal (outside press / Escape), and ARIA wiring, but ships **no styles** β€” you bring
your own CSS by targeting the `data-cl-*` state attributes each part emits.

## Example

Expand All@@ -26,13 +26,14 @@ import { Dialog } from '@clerk/headless/dialog';
<Dialog.Root>
<Dialog.Trigger>Open</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Backdrop>
<Dialog.Backdrop />
<Dialog.Viewport>
<Dialog.Popup>
<Dialog.Title>Confirm action</Dialog.Title>
<Dialog.Description>Are you sure you want to proceed?</Dialog.Description>
<Dialog.Close>Cancel</Dialog.Close>
</Dialog.Popup>
</Dialog.Backdrop>
</Dialog.Viewport>
</Dialog.Portal>
</Dialog.Root>;
```
Expand All@@ -50,23 +51,30 @@ const [open, setOpen] = useState(false);
</Dialog.Root>;
```

### Non-modal (page stays interactive)

```tsx
<Dialog.Root modal={false}>{/* focus is not trapped and the rest of the page stays interactive */}</Dialog.Root>
```

## Parts

| Part | Default Element | Description |
| -------------------- | --------------- | --------------------------------------------------------------- |
| `Dialog.Root` | none (context) | Owns open state, ARIA ids, and the transition lifecycle |
| `Dialog.Trigger` | `<button>` | Toggles the dialog open on click |
| `Dialog.Portal` | none (portal) | Portals its children; renders nothing until mounted |
| `Dialog.Backdrop` | `<div>` | Overlay; hosts the fixed backdrop and optional body scroll lock |
| `Dialog.Popup` | `<div>` | The dialog content container (`role="dialog"`, focus-trapped) |
| `Dialog.Title` | `<h2>` | Heading; wired to the popup's `aria-labelledby` |
| `Dialog.Description` | `<p>` | Description; wired to the popup's `aria-describedby` |
| `Dialog.Close` | `<button>` | Closes the dialog on click |
| Part | Default Element | Description |
| -------------------- | --------------- | ------------------------------------------------------------------- |
| `Dialog.Root` | none (context) | Owns open state, ARIA ids, modal mode, and the transition lifecycle |
| `Dialog.Trigger` | `<button>` | Toggles the dialog open on click |
| `Dialog.Portal` | none (portal) | Portals its children; renders nothing until mounted |
| `Dialog.Backdrop` | `<div>` | Semi-transparent overlay surface behind the popup |
| `Dialog.Viewport` | `<div>` | Fixed centering container; owns body scroll lock |
| `Dialog.Popup` | `<div>` | The dialog content container (`role="dialog"`, focus-trapped) |
| `Dialog.Title` | `<h2>` | Heading; wired to the popup's `aria-labelledby` |
| `Dialog.Description` | `<p>` | Description; wired to the popup's `aria-describedby` |
| `Dialog.Close` | `<button>` | Closes the dialog on click |

All rendered parts accept a `render` prop for polymorphic rendering and standard HTML
attributes for their default element. `Dialog.Title` and `Dialog.Description` manage their
own `id`. `Dialog.Portal` is optional β€” `Dialog.Backdrop` may be nested directly under
`Dialog.Root`.
attributes for their default element. Compound parts throw if used outside `Dialog.Root`.
`Dialog.Title` and `Dialog.Description` manage their own `id`. `Dialog.Portal` is optional;
for centered, scroll-locked modal behavior nest `Dialog.Popup` inside `Dialog.Viewport`.

## Props

Expand All@@ -81,41 +89,43 @@ own `id`. `Dialog.Portal` is optional β€” `Dialog.Backdrop` may be nested direct

### `Dialog.Portal`

| Prop | Type | Default | Description |
| ----------------- | ---------------------------------------------------------------- | -------------------------- | ---------------------------------------------------------------------------------------------------- |
| <code>root</code> | <code>HTMLElement \| null \| RefObject&lt;HTMLElement&gt;</code> | <code>document.body</code> | Container to portal into; non-null switches the backdrop to "scoped" mode (no overlay / scroll lock) |
| Prop | Type | Default | Description |
| ----------------- | ---------------------------------------------------------------- | -------------------------- | -------------------------------- |
| <code>root</code> | <code>HTMLElement \| null \| RefObject&lt;HTMLElement&gt;</code> | <code>document.body</code> | Container element to portal into |

### `Dialog.Backdrop`
### `Dialog.Viewport`

| Prop | Type | Default | Description |
| ----------------------- | -------------------- | ----------------- | ----------------------------------------- |
| <code>lockScroll</code> | <code>boolean</code> | <code>true</code> | Lock body scroll while the dialog is open |

`Dialog.Trigger`, `Dialog.Popup`, `Dialog.Title`, `Dialog.Description`, and `Dialog.Close`
take no additional props beyond standard HTML attributes for their default element.
`Dialog.Trigger`, `Dialog.Backdrop`, `Dialog.Popup`, `Dialog.Title`, `Dialog.Description`,
and `Dialog.Close` take no additional props beyond standard HTML attributes for their
default element.

## Styling

Each part emits `data-cl-*` attributes you can target with any CSS solution:
The headless parts don't emit `data-cl-slot` β€” slot identity is applied by the styled
(Mosaic) layer. Target a part with your own class (or `render` prop) and combine it with
the `data-cl-*` state attributes each part emits:

| Attribute | Applies To | Description |
| ------------------------ | ------------------------ | ------------------------------------------------- |
| `data-cl-slot` | All parts | Part identifier |
| `data-cl-open` | Trigger, Backdrop, Popup | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Popup | Present when closed (still mounted while exiting) |
| `data-cl-starting-style` | Backdrop, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Popup | Present during the exit animation |
| Attribute | Applies To | Description |
| ------------------------ | ---------------------------------- | ------------------------------------------------- |
| `data-cl-open` | Trigger, Backdrop, Viewport, Popup | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Viewport, Popup | Present when closed (still mounted while exiting) |
| `data-cl-starting-style` | Backdrop, Viewport, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Viewport, Popup | Present during the exit animation |

The backdropand popup stay mounted through the exit animation, so enter/exit transitions
are CSS-driven:
The backdrop, viewport, and popup stay mounted through the exit animation, so enter/exit
transitions are CSS-driven:

```css
[data-cl-slot='dialog-popup'] {
.dialog-popup {
opacity: 1;
transition: opacity 150ms ease;
}
[data-cl-slot='dialog-popup'][data-cl-starting-style],
[data-cl-slot='dialog-popup'][data-cl-ending-style] {
.dialog-popup[data-cl-starting-style],
.dialog-popup[data-cl-ending-style] {
opacity: 0;
}
```
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/bold-horses-rhyme.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
---
---
44 changes: 23 additions & 21 deletions packages/swingset/src/stories/dialog.component.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,13 +7,27 @@ primitives, composed with Mosaic slot recipes. It flattens the required nesting
Backdrop, Viewport, Popup) into a single component and exposes a `close` callback through a
render-prop children pattern.

## Example
## Playground

<Story
<Preview
name='Default'
storyModule={DialogStories}
/>

## Props

<PropTable
meta={DialogStories.meta}
extra={[
{ name: 'trigger', type: '(props: HTMLAttributes<HTMLElement>) => ReactElement' },
{ name: 'children', type: 'ReactNode | ((ctx: { close: () => void }) => ReactNode)' },
{ name: 'open', type: 'boolean' },
{ name: 'defaultOpen', type: 'boolean', default: 'false' },
{ name: 'onOpenChange', type: '(open: boolean) => void' },
{ name: 'modal', type: 'boolean', default: 'true' },
]}
Comment on lines +19 to +28

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor | ⚑ Quick win

Provide defaults for every prop in the generated props table input.

In extra, several props (trigger, children, open, onOpenChange) do not declare a default. Please set explicit defaults (typically β€”) so the rendered table stays compliant and unambiguous.

As per coding guidelines: β€œIn all props tables, document the default value for every prop in a dedicated Default column … use β€” when there is no default.”

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/swingset/src/stories/dialog.component.mdx` around lines 19 - 28, The
PropTable component in the dialog.component.mdx file has an extra array with
prop definitions where some props are missing the default field. Add a default
property to each prop object in the extra array that currently lacks one
(trigger, children, open, and onOpenChange). For props without a meaningful
default value, use "β€”" as the default to comply with the props table
documentation standards. Ensure all prop objects in the extra array now include
an explicit default field.

Source: Coding guidelines

/>

## Usage
Comment on lines +10 to 31

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚑ Quick win

Add the required Examples section for Archetype A docs.

This page currently has Playground β†’ Props β†’ Usage but omits Examples, which is required for Archetype A MDX docs.

As per coding guidelines: β€œFor Archetype A (simple CVA) MDX files, required sections in order: Playground, Props, Usage, then Examples. Never reorder or omit these sections.”

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/swingset/src/stories/dialog.component.mdx` around lines 10 - 31, The
dialog.component.mdx file is missing the required Examples section which is
mandatory for Archetype A documentation. The file currently contains Playground,
Props, and Usage sections, but according to guidelines, the Examples section
must be included after Usage. Add an Examples section after the Usage section
heading with relevant code examples demonstrating how to use the Dialog
component in different scenarios.

Source: Coding guidelines


```tsx
Expand DownExpand Up@@ -63,18 +77,6 @@ const [open, setOpen] = useState(false);
</Dialog>
```

## Props

| Prop | Type | Default | Description |
| -------------- | ---------------------------------------------------------- | ------- | ------------------------------------------------- |
| `trigger` | `(props: HTMLAttributes<HTMLElement>) => ReactElement` | β€” | Render prop for the trigger element |
| `children` | `ReactNode \| ((ctx: { close: () => void }) => ReactNode)` | β€” | Dialog content; use render prop to access `close` |
| `size` | `'md' \| 'lg'` | `'md'` | Controls the popup width |
| `open` | `boolean` | β€” | Controlled open state |
| `defaultOpen` | `boolean` | `false` | Initial open state (uncontrolled) |
| `onOpenChange` | `(open: boolean) => void` | β€” | Called when the open state changes |
| `modal` | `boolean` | `true` | Trap focus and make the rest of the page inert |

## Sub-parts

| Part | Slot | Description |
Expand All@@ -97,10 +99,10 @@ The Mosaic dialog exposes the following slots that can be styled via `appearance
Override per slot through `appearance.elements` β€” e.g. `{ 'dialog-popup': { borderRadius: 24 } }`.
State attributes from the headless layer are also available for CSS targeting:

| Attribute | Applies To | Description |
| ------------------------ | --------------------------- | --------------------------------- |
| `data-cl-slot` | All parts | Part identifier |
| `data-cl-open` | Trigger, Backdrop, Viewport | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Viewport | Present when closed (during exit) |
| `data-cl-starting-style` | Backdrop, Viewport, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Viewport, Popup | Present during the exit animation |
| Attribute | Applies To | Description |
| ------------------------ | ---------------------------------- | --------------------------------- |
| `data-cl-slot` | All parts | Part identifier |
| `data-cl-open` | Trigger, Backdrop, Viewport, Popup | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Viewport, Popup | Present when closed (during exit) |
| `data-cl-starting-style` | Backdrop, Viewport, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Viewport, Popup | Present during the exit animation |
84 changes: 47 additions & 37 deletions packages/swingset/src/stories/dialog.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,9 +4,9 @@ import * as DialogStories from './dialog.stories';

A modal window that overlays the page and traps focus until dismissed, from
`@clerk/headless`. It is a **headless** primitive: it supplies open state, portalling, an
Comment on lines 5 to 6

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor | ⚑ Quick win

Clarify focus-trap wording as modal-only behavior.

The intro and Dialog.Popup description read as unconditional focus trapping, but this page also documents modal={false} where focus is not trapped. Please qualify these lines as β€œin modal mode” (or β€œby default”).

Also applies to: 69-69

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/swingset/src/stories/dialog.mdx` around lines 5 - 6, The
documentation in the Dialog component description incorrectly presents focus
trapping as unconditional behavior, but the component supports a modal={false}
option where focus is not trapped. Locate the introductory description around
line 5-6 that states the component "traps focus until dismissed" and the
Dialog.Popup description around line 69, then add qualifying language such as
"in modal mode" or "by default" to both locations to clarify that focus trapping
only occurs when the modal prop is enabled. This ensures the documentation
accurately reflects that focus trapping is conditional behavior rather than a
default always-on feature.

overlay with optional scroll lock, focus management, dismissal (outside press / Escape),
and ARIA wiring, but ships **no styles** β€” you bring your own CSS by targeting the
`data-cl-*` attributes each part emits.
overlay surface, a centering viewport with optional body scroll lock, focus management,
dismissal (outside press / Escape), and ARIA wiring, but ships **no styles** β€” you bring
your own CSS by targeting the `data-cl-*` state attributes each part emits.

## Example

Expand All@@ -26,13 +26,14 @@ import { Dialog } from '@clerk/headless/dialog';
<Dialog.Root>
<Dialog.Trigger>Open</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Backdrop>
<Dialog.Backdrop />
<Dialog.Viewport>
<Dialog.Popup>
<Dialog.Title>Confirm action</Dialog.Title>
<Dialog.Description>Are you sure you want to proceed?</Dialog.Description>
<Dialog.Close>Cancel</Dialog.Close>
</Dialog.Popup>
</Dialog.Backdrop>
</Dialog.Viewport>
</Dialog.Portal>
</Dialog.Root>;
```
Expand All@@ -50,23 +51,30 @@ const [open, setOpen] = useState(false);
</Dialog.Root>;
```

### Non-modal (page stays interactive)

```tsx
<Dialog.Root modal={false}>{/* focus is not trapped and the rest of the page stays interactive */}</Dialog.Root>
```

## Parts

| Part | Default Element | Description |
| -------------------- | --------------- | --------------------------------------------------------------- |
| `Dialog.Root` | none (context) | Owns open state, ARIA ids, and the transition lifecycle |
| `Dialog.Trigger` | `<button>` | Toggles the dialog open on click |
| `Dialog.Portal` | none (portal) | Portals its children; renders nothing until mounted |
| `Dialog.Backdrop` | `<div>` | Overlay; hosts the fixed backdrop and optional body scroll lock |
| `Dialog.Popup` | `<div>` | The dialog content container (`role="dialog"`, focus-trapped) |
| `Dialog.Title` | `<h2>` | Heading; wired to the popup's `aria-labelledby` |
| `Dialog.Description` | `<p>` | Description; wired to the popup's `aria-describedby` |
| `Dialog.Close` | `<button>` | Closes the dialog on click |
| Part | Default Element | Description |
| -------------------- | --------------- | ------------------------------------------------------------------- |
| `Dialog.Root` | none (context) | Owns open state, ARIA ids, modal mode, and the transition lifecycle |
| `Dialog.Trigger` | `<button>` | Toggles the dialog open on click |
| `Dialog.Portal` | none (portal) | Portals its children; renders nothing until mounted |
| `Dialog.Backdrop` | `<div>` | Semi-transparent overlay surface behind the popup |
| `Dialog.Viewport` | `<div>` | Fixed centering container; owns body scroll lock |
| `Dialog.Popup` | `<div>` | The dialog content container (`role="dialog"`, focus-trapped) |
| `Dialog.Title` | `<h2>` | Heading; wired to the popup's `aria-labelledby` |
| `Dialog.Description` | `<p>` | Description; wired to the popup's `aria-describedby` |
| `Dialog.Close` | `<button>` | Closes the dialog on click |

All rendered parts accept a `render` prop for polymorphic rendering and standard HTML
attributes for their default element. `Dialog.Title` and `Dialog.Description` manage their
own `id`. `Dialog.Portal` is optional β€” `Dialog.Backdrop` may be nested directly under
`Dialog.Root`.
attributes for their default element. Compound parts throw if used outside `Dialog.Root`.
`Dialog.Title` and `Dialog.Description` manage their own `id`. `Dialog.Portal` is optional;
for centered, scroll-locked modal behavior nest `Dialog.Popup` inside `Dialog.Viewport`.

## Props

Expand All@@ -81,41 +89,43 @@ own `id`. `Dialog.Portal` is optional β€” `Dialog.Backdrop` may be nested direct

### `Dialog.Portal`

| Prop | Type | Default | Description |
| ----------------- | ---------------------------------------------------------------- | -------------------------- | ---------------------------------------------------------------------------------------------------- |
| <code>root</code> | <code>HTMLElement \| null \| RefObject&lt;HTMLElement&gt;</code> | <code>document.body</code> | Container to portal into; non-null switches the backdrop to "scoped" mode (no overlay / scroll lock) |
| Prop | Type | Default | Description |
| ----------------- | ---------------------------------------------------------------- | -------------------------- | -------------------------------- |
| <code>root</code> | <code>HTMLElement \| null \| RefObject&lt;HTMLElement&gt;</code> | <code>document.body</code> | Container element to portal into |

### `Dialog.Backdrop`
### `Dialog.Viewport`

| Prop | Type | Default | Description |
| ----------------------- | -------------------- | ----------------- | ----------------------------------------- |
| <code>lockScroll</code> | <code>boolean</code> | <code>true</code> | Lock body scroll while the dialog is open |

`Dialog.Trigger`, `Dialog.Popup`, `Dialog.Title`, `Dialog.Description`, and `Dialog.Close`
take no additional props beyond standard HTML attributes for their default element.
`Dialog.Trigger`, `Dialog.Backdrop`, `Dialog.Popup`, `Dialog.Title`, `Dialog.Description`,
and `Dialog.Close` take no additional props beyond standard HTML attributes for their
default element.

## Styling

Each part emits `data-cl-*` attributes you can target with any CSS solution:
The headless parts don't emit `data-cl-slot` β€” slot identity is applied by the styled
(Mosaic) layer. Target a part with your own class (or `render` prop) and combine it with
the `data-cl-*` state attributes each part emits:

| Attribute | Applies To | Description |
| ------------------------ | ------------------------ | ------------------------------------------------- |
| `data-cl-slot` | All parts | Part identifier |
| `data-cl-open` | Trigger, Backdrop, Popup | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Popup | Present when closed (still mounted while exiting) |
| `data-cl-starting-style` | Backdrop, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Popup | Present during the exit animation |
| Attribute | Applies To | Description |
| ------------------------ | ---------------------------------- | ------------------------------------------------- |
| `data-cl-open` | Trigger, Backdrop, Viewport, Popup | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Viewport, Popup | Present when closed (still mounted while exiting) |
| `data-cl-starting-style` | Backdrop, Viewport, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Viewport, Popup | Present during the exit animation |

The backdropand popup stay mounted through the exit animation, so enter/exit transitions
are CSS-driven:
The backdrop, viewport, and popup stay mounted through the exit animation, so enter/exit
transitions are CSS-driven:

```css
[data-cl-slot='dialog-popup'] {
.dialog-popup {
opacity: 1;
transition: opacity 150ms ease;
}
[data-cl-slot='dialog-popup'][data-cl-starting-style],
[data-cl-slot='dialog-popup'][data-cl-ending-style] {
.dialog-popup[data-cl-starting-style],
.dialog-popup[data-cl-ending-style] {
opacity: 0;
}
```
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/bold-horses-rhyme.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
---
---
44 changes: 23 additions & 21 deletions packages/swingset/src/stories/dialog.component.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,13 +7,27 @@ primitives, composed with Mosaic slot recipes. It flattens the required nesting
Backdrop, Viewport, Popup) into a single component and exposes a `close` callback through a
render-prop children pattern.

## Example
## Playground

<Story
<Preview
name='Default'
storyModule={DialogStories}
/>

## Props

<PropTable
meta={DialogStories.meta}
extra={[
{ name: 'trigger', type: '(props: HTMLAttributes<HTMLElement>) => ReactElement' },
{ name: 'children', type: 'ReactNode | ((ctx: { close: () => void }) => ReactNode)' },
{ name: 'open', type: 'boolean' },
{ name: 'defaultOpen', type: 'boolean', default: 'false' },
{ name: 'onOpenChange', type: '(open: boolean) => void' },
{ name: 'modal', type: 'boolean', default: 'true' },
]}
Comment on lines +19 to +28

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor | ⚑ Quick win

Provide defaults for every prop in the generated props table input.

In extra, several props (trigger, children, open, onOpenChange) do not declare a default. Please set explicit defaults (typically β€”) so the rendered table stays compliant and unambiguous.

As per coding guidelines: β€œIn all props tables, document the default value for every prop in a dedicated Default column … use β€” when there is no default.”

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/swingset/src/stories/dialog.component.mdx` around lines 19 - 28, The
PropTable component in the dialog.component.mdx file has an extra array with
prop definitions where some props are missing the default field. Add a default
property to each prop object in the extra array that currently lacks one
(trigger, children, open, and onOpenChange). For props without a meaningful
default value, use "β€”" as the default to comply with the props table
documentation standards. Ensure all prop objects in the extra array now include
an explicit default field.

Source: Coding guidelines

/>

## Usage
Comment on lines +10 to 31

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚑ Quick win

Add the required Examples section for Archetype A docs.

This page currently has Playground β†’ Props β†’ Usage but omits Examples, which is required for Archetype A MDX docs.

As per coding guidelines: β€œFor Archetype A (simple CVA) MDX files, required sections in order: Playground, Props, Usage, then Examples. Never reorder or omit these sections.”

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/swingset/src/stories/dialog.component.mdx` around lines 10 - 31, The
dialog.component.mdx file is missing the required Examples section which is
mandatory for Archetype A documentation. The file currently contains Playground,
Props, and Usage sections, but according to guidelines, the Examples section
must be included after Usage. Add an Examples section after the Usage section
heading with relevant code examples demonstrating how to use the Dialog
component in different scenarios.

Source: Coding guidelines


```tsx
Expand DownExpand Up@@ -63,18 +77,6 @@ const [open, setOpen] = useState(false);
</Dialog>
```

## Props

| Prop | Type | Default | Description |
| -------------- | ---------------------------------------------------------- | ------- | ------------------------------------------------- |
| `trigger` | `(props: HTMLAttributes<HTMLElement>) => ReactElement` | β€” | Render prop for the trigger element |
| `children` | `ReactNode \| ((ctx: { close: () => void }) => ReactNode)` | β€” | Dialog content; use render prop to access `close` |
| `size` | `'md' \| 'lg'` | `'md'` | Controls the popup width |
| `open` | `boolean` | β€” | Controlled open state |
| `defaultOpen` | `boolean` | `false` | Initial open state (uncontrolled) |
| `onOpenChange` | `(open: boolean) => void` | β€” | Called when the open state changes |
| `modal` | `boolean` | `true` | Trap focus and make the rest of the page inert |

## Sub-parts

| Part | Slot | Description |
Expand All@@ -97,10 +99,10 @@ The Mosaic dialog exposes the following slots that can be styled via `appearance
Override per slot through `appearance.elements` β€” e.g. `{ 'dialog-popup': { borderRadius: 24 } }`.
State attributes from the headless layer are also available for CSS targeting:

| Attribute | Applies To | Description |
| ------------------------ | --------------------------- | --------------------------------- |
| `data-cl-slot` | All parts | Part identifier |
| `data-cl-open` | Trigger, Backdrop, Viewport | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Viewport | Present when closed (during exit) |
| `data-cl-starting-style` | Backdrop, Viewport, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Viewport, Popup | Present during the exit animation |
| Attribute | Applies To | Description |
| ------------------------ | ---------------------------------- | --------------------------------- |
| `data-cl-slot` | All parts | Part identifier |
| `data-cl-open` | Trigger, Backdrop, Viewport, Popup | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Viewport, Popup | Present when closed (during exit) |
| `data-cl-starting-style` | Backdrop, Viewport, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Viewport, Popup | Present during the exit animation |
84 changes: 47 additions & 37 deletions packages/swingset/src/stories/dialog.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,9 +4,9 @@ import * as DialogStories from './dialog.stories';

A modal window that overlays the page and traps focus until dismissed, from
`@clerk/headless`. It is a **headless** primitive: it supplies open state, portalling, an
Comment on lines 5 to 6

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor | ⚑ Quick win

Clarify focus-trap wording as modal-only behavior.

The intro and Dialog.Popup description read as unconditional focus trapping, but this page also documents modal={false} where focus is not trapped. Please qualify these lines as β€œin modal mode” (or β€œby default”).

Also applies to: 69-69

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/swingset/src/stories/dialog.mdx` around lines 5 - 6, The
documentation in the Dialog component description incorrectly presents focus
trapping as unconditional behavior, but the component supports a modal={false}
option where focus is not trapped. Locate the introductory description around
line 5-6 that states the component "traps focus until dismissed" and the
Dialog.Popup description around line 69, then add qualifying language such as
"in modal mode" or "by default" to both locations to clarify that focus trapping
only occurs when the modal prop is enabled. This ensures the documentation
accurately reflects that focus trapping is conditional behavior rather than a
default always-on feature.

overlay with optional scroll lock, focus management, dismissal (outside press / Escape),
and ARIA wiring, but ships **no styles** β€” you bring your own CSS by targeting the
`data-cl-*` attributes each part emits.
overlay surface, a centering viewport with optional body scroll lock, focus management,
dismissal (outside press / Escape), and ARIA wiring, but ships **no styles** β€” you bring
your own CSS by targeting the `data-cl-*` state attributes each part emits.

## Example

Expand All@@ -26,13 +26,14 @@ import { Dialog } from '@clerk/headless/dialog';
<Dialog.Root>
<Dialog.Trigger>Open</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Backdrop>
<Dialog.Backdrop />
<Dialog.Viewport>
<Dialog.Popup>
<Dialog.Title>Confirm action</Dialog.Title>
<Dialog.Description>Are you sure you want to proceed?</Dialog.Description>
<Dialog.Close>Cancel</Dialog.Close>
</Dialog.Popup>
</Dialog.Backdrop>
</Dialog.Viewport>
</Dialog.Portal>
</Dialog.Root>;
```
Expand All@@ -50,23 +51,30 @@ const [open, setOpen] = useState(false);
</Dialog.Root>;
```

### Non-modal (page stays interactive)

```tsx
<Dialog.Root modal={false}>{/* focus is not trapped and the rest of the page stays interactive */}</Dialog.Root>
```

## Parts

| Part | Default Element | Description |
| -------------------- | --------------- | --------------------------------------------------------------- |
| `Dialog.Root` | none (context) | Owns open state, ARIA ids, and the transition lifecycle |
| `Dialog.Trigger` | `<button>` | Toggles the dialog open on click |
| `Dialog.Portal` | none (portal) | Portals its children; renders nothing until mounted |
| `Dialog.Backdrop` | `<div>` | Overlay; hosts the fixed backdrop and optional body scroll lock |
| `Dialog.Popup` | `<div>` | The dialog content container (`role="dialog"`, focus-trapped) |
| `Dialog.Title` | `<h2>` | Heading; wired to the popup's `aria-labelledby` |
| `Dialog.Description` | `<p>` | Description; wired to the popup's `aria-describedby` |
| `Dialog.Close` | `<button>` | Closes the dialog on click |
| Part | Default Element | Description |
| -------------------- | --------------- | ------------------------------------------------------------------- |
| `Dialog.Root` | none (context) | Owns open state, ARIA ids, modal mode, and the transition lifecycle |
| `Dialog.Trigger` | `<button>` | Toggles the dialog open on click |
| `Dialog.Portal` | none (portal) | Portals its children; renders nothing until mounted |
| `Dialog.Backdrop` | `<div>` | Semi-transparent overlay surface behind the popup |
| `Dialog.Viewport` | `<div>` | Fixed centering container; owns body scroll lock |
| `Dialog.Popup` | `<div>` | The dialog content container (`role="dialog"`, focus-trapped) |
| `Dialog.Title` | `<h2>` | Heading; wired to the popup's `aria-labelledby` |
| `Dialog.Description` | `<p>` | Description; wired to the popup's `aria-describedby` |
| `Dialog.Close` | `<button>` | Closes the dialog on click |

All rendered parts accept a `render` prop for polymorphic rendering and standard HTML
attributes for their default element. `Dialog.Title` and `Dialog.Description` manage their
own `id`. `Dialog.Portal` is optional β€” `Dialog.Backdrop` may be nested directly under
`Dialog.Root`.
attributes for their default element. Compound parts throw if used outside `Dialog.Root`.
`Dialog.Title` and `Dialog.Description` manage their own `id`. `Dialog.Portal` is optional;
for centered, scroll-locked modal behavior nest `Dialog.Popup` inside `Dialog.Viewport`.

## Props

Expand All@@ -81,41 +89,43 @@ own `id`. `Dialog.Portal` is optional β€” `Dialog.Backdrop` may be nested direct

### `Dialog.Portal`

| Prop | Type | Default | Description |
| ----------------- | ---------------------------------------------------------------- | -------------------------- | ---------------------------------------------------------------------------------------------------- |
| <code>root</code> | <code>HTMLElement \| null \| RefObject&lt;HTMLElement&gt;</code> | <code>document.body</code> | Container to portal into; non-null switches the backdrop to "scoped" mode (no overlay / scroll lock) |
| Prop | Type | Default | Description |
| ----------------- | ---------------------------------------------------------------- | -------------------------- | -------------------------------- |
| <code>root</code> | <code>HTMLElement \| null \| RefObject&lt;HTMLElement&gt;</code> | <code>document.body</code> | Container element to portal into |

### `Dialog.Backdrop`
### `Dialog.Viewport`

| Prop | Type | Default | Description |
| ----------------------- | -------------------- | ----------------- | ----------------------------------------- |
| <code>lockScroll</code> | <code>boolean</code> | <code>true</code> | Lock body scroll while the dialog is open |

`Dialog.Trigger`, `Dialog.Popup`, `Dialog.Title`, `Dialog.Description`, and `Dialog.Close`
take no additional props beyond standard HTML attributes for their default element.
`Dialog.Trigger`, `Dialog.Backdrop`, `Dialog.Popup`, `Dialog.Title`, `Dialog.Description`,
and `Dialog.Close` take no additional props beyond standard HTML attributes for their
default element.

## Styling

Each part emits `data-cl-*` attributes you can target with any CSS solution:
The headless parts don't emit `data-cl-slot` β€” slot identity is applied by the styled
(Mosaic) layer. Target a part with your own class (or `render` prop) and combine it with
the `data-cl-*` state attributes each part emits:

| Attribute | Applies To | Description |
| ------------------------ | ------------------------ | ------------------------------------------------- |
| `data-cl-slot` | All parts | Part identifier |
| `data-cl-open` | Trigger, Backdrop, Popup | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Popup | Present when closed (still mounted while exiting) |
| `data-cl-starting-style` | Backdrop, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Popup | Present during the exit animation |
| Attribute | Applies To | Description |
| ------------------------ | ---------------------------------- | ------------------------------------------------- |
| `data-cl-open` | Trigger, Backdrop, Viewport, Popup | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Viewport, Popup | Present when closed (still mounted while exiting) |
| `data-cl-starting-style` | Backdrop, Viewport, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Viewport, Popup | Present during the exit animation |

The backdropand popup stay mounted through the exit animation, so enter/exit transitions
are CSS-driven:
The backdrop, viewport, and popup stay mounted through the exit animation, so enter/exit
transitions are CSS-driven:

```css
[data-cl-slot='dialog-popup'] {
.dialog-popup {
opacity: 1;
transition: opacity 150ms ease;
}
[data-cl-slot='dialog-popup'][data-cl-starting-style],
[data-cl-slot='dialog-popup'][data-cl-ending-style] {
.dialog-popup[data-cl-starting-style],
.dialog-popup[data-cl-ending-style] {
opacity: 0;
}
```
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/bold-horses-rhyme.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
---
---
44 changes: 23 additions & 21 deletions packages/swingset/src/stories/dialog.component.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,13 +7,27 @@ primitives, composed with Mosaic slot recipes. It flattens the required nesting
Backdrop, Viewport, Popup) into a single component and exposes a `close` callback through a
render-prop children pattern.

## Example
## Playground

<Story
<Preview
name='Default'
storyModule={DialogStories}
/>

## Props

<PropTable
meta={DialogStories.meta}
extra={[
{ name: 'trigger', type: '(props: HTMLAttributes<HTMLElement>) => ReactElement' },
{ name: 'children', type: 'ReactNode | ((ctx: { close: () => void }) => ReactNode)' },
{ name: 'open', type: 'boolean' },
{ name: 'defaultOpen', type: 'boolean', default: 'false' },
{ name: 'onOpenChange', type: '(open: boolean) => void' },
{ name: 'modal', type: 'boolean', default: 'true' },
]}
Comment on lines +19 to +28

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor | ⚑ Quick win

Provide defaults for every prop in the generated props table input.

In extra, several props (trigger, children, open, onOpenChange) do not declare a default. Please set explicit defaults (typically β€”) so the rendered table stays compliant and unambiguous.

As per coding guidelines: β€œIn all props tables, document the default value for every prop in a dedicated Default column … use β€” when there is no default.”

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/swingset/src/stories/dialog.component.mdx` around lines 19 - 28, The
PropTable component in the dialog.component.mdx file has an extra array with
prop definitions where some props are missing the default field. Add a default
property to each prop object in the extra array that currently lacks one
(trigger, children, open, and onOpenChange). For props without a meaningful
default value, use "β€”" as the default to comply with the props table
documentation standards. Ensure all prop objects in the extra array now include
an explicit default field.

Source: Coding guidelines

/>

## Usage
Comment on lines +10 to 31

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚑ Quick win

Add the required Examples section for Archetype A docs.

This page currently has Playground β†’ Props β†’ Usage but omits Examples, which is required for Archetype A MDX docs.

As per coding guidelines: β€œFor Archetype A (simple CVA) MDX files, required sections in order: Playground, Props, Usage, then Examples. Never reorder or omit these sections.”

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/swingset/src/stories/dialog.component.mdx` around lines 10 - 31, The
dialog.component.mdx file is missing the required Examples section which is
mandatory for Archetype A documentation. The file currently contains Playground,
Props, and Usage sections, but according to guidelines, the Examples section
must be included after Usage. Add an Examples section after the Usage section
heading with relevant code examples demonstrating how to use the Dialog
component in different scenarios.

Source: Coding guidelines


```tsx
Expand DownExpand Up@@ -63,18 +77,6 @@ const [open, setOpen] = useState(false);
</Dialog>
```

## Props

| Prop | Type | Default | Description |
| -------------- | ---------------------------------------------------------- | ------- | ------------------------------------------------- |
| `trigger` | `(props: HTMLAttributes<HTMLElement>) => ReactElement` | β€” | Render prop for the trigger element |
| `children` | `ReactNode \| ((ctx: { close: () => void }) => ReactNode)` | β€” | Dialog content; use render prop to access `close` |
| `size` | `'md' \| 'lg'` | `'md'` | Controls the popup width |
| `open` | `boolean` | β€” | Controlled open state |
| `defaultOpen` | `boolean` | `false` | Initial open state (uncontrolled) |
| `onOpenChange` | `(open: boolean) => void` | β€” | Called when the open state changes |
| `modal` | `boolean` | `true` | Trap focus and make the rest of the page inert |

## Sub-parts

| Part | Slot | Description |
Expand All@@ -97,10 +99,10 @@ The Mosaic dialog exposes the following slots that can be styled via `appearance
Override per slot through `appearance.elements` β€” e.g. `{ 'dialog-popup': { borderRadius: 24 } }`.
State attributes from the headless layer are also available for CSS targeting:

| Attribute | Applies To | Description |
| ------------------------ | --------------------------- | --------------------------------- |
| `data-cl-slot` | All parts | Part identifier |
| `data-cl-open` | Trigger, Backdrop, Viewport | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Viewport | Present when closed (during exit) |
| `data-cl-starting-style` | Backdrop, Viewport, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Viewport, Popup | Present during the exit animation |
| Attribute | Applies To | Description |
| ------------------------ | ---------------------------------- | --------------------------------- |
| `data-cl-slot` | All parts | Part identifier |
| `data-cl-open` | Trigger, Backdrop, Viewport, Popup | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Viewport, Popup | Present when closed (during exit) |
| `data-cl-starting-style` | Backdrop, Viewport, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Viewport, Popup | Present during the exit animation |
84 changes: 47 additions & 37 deletions packages/swingset/src/stories/dialog.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,9 +4,9 @@ import * as DialogStories from './dialog.stories';

A modal window that overlays the page and traps focus until dismissed, from
`@clerk/headless`. It is a **headless** primitive: it supplies open state, portalling, an
Comment on lines 5 to 6

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor | ⚑ Quick win

Clarify focus-trap wording as modal-only behavior.

The intro and Dialog.Popup description read as unconditional focus trapping, but this page also documents modal={false} where focus is not trapped. Please qualify these lines as β€œin modal mode” (or β€œby default”).

Also applies to: 69-69

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/swingset/src/stories/dialog.mdx` around lines 5 - 6, The
documentation in the Dialog component description incorrectly presents focus
trapping as unconditional behavior, but the component supports a modal={false}
option where focus is not trapped. Locate the introductory description around
line 5-6 that states the component "traps focus until dismissed" and the
Dialog.Popup description around line 69, then add qualifying language such as
"in modal mode" or "by default" to both locations to clarify that focus trapping
only occurs when the modal prop is enabled. This ensures the documentation
accurately reflects that focus trapping is conditional behavior rather than a
default always-on feature.

overlay with optional scroll lock, focus management, dismissal (outside press / Escape),
and ARIA wiring, but ships **no styles** β€” you bring your own CSS by targeting the
`data-cl-*` attributes each part emits.
overlay surface, a centering viewport with optional body scroll lock, focus management,
dismissal (outside press / Escape), and ARIA wiring, but ships **no styles** β€” you bring
your own CSS by targeting the `data-cl-*` state attributes each part emits.

## Example

Expand All@@ -26,13 +26,14 @@ import { Dialog } from '@clerk/headless/dialog';
<Dialog.Root>
<Dialog.Trigger>Open</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Backdrop>
<Dialog.Backdrop />
<Dialog.Viewport>
<Dialog.Popup>
<Dialog.Title>Confirm action</Dialog.Title>
<Dialog.Description>Are you sure you want to proceed?</Dialog.Description>
<Dialog.Close>Cancel</Dialog.Close>
</Dialog.Popup>
</Dialog.Backdrop>
</Dialog.Viewport>
</Dialog.Portal>
</Dialog.Root>;
```
Expand All@@ -50,23 +51,30 @@ const [open, setOpen] = useState(false);
</Dialog.Root>;
```

### Non-modal (page stays interactive)

```tsx
<Dialog.Root modal={false}>{/* focus is not trapped and the rest of the page stays interactive */}</Dialog.Root>
```

## Parts

| Part | Default Element | Description |
| -------------------- | --------------- | --------------------------------------------------------------- |
| `Dialog.Root` | none (context) | Owns open state, ARIA ids, and the transition lifecycle |
| `Dialog.Trigger` | `<button>` | Toggles the dialog open on click |
| `Dialog.Portal` | none (portal) | Portals its children; renders nothing until mounted |
| `Dialog.Backdrop` | `<div>` | Overlay; hosts the fixed backdrop and optional body scroll lock |
| `Dialog.Popup` | `<div>` | The dialog content container (`role="dialog"`, focus-trapped) |
| `Dialog.Title` | `<h2>` | Heading; wired to the popup's `aria-labelledby` |
| `Dialog.Description` | `<p>` | Description; wired to the popup's `aria-describedby` |
| `Dialog.Close` | `<button>` | Closes the dialog on click |
| Part | Default Element | Description |
| -------------------- | --------------- | ------------------------------------------------------------------- |
| `Dialog.Root` | none (context) | Owns open state, ARIA ids, modal mode, and the transition lifecycle |
| `Dialog.Trigger` | `<button>` | Toggles the dialog open on click |
| `Dialog.Portal` | none (portal) | Portals its children; renders nothing until mounted |
| `Dialog.Backdrop` | `<div>` | Semi-transparent overlay surface behind the popup |
| `Dialog.Viewport` | `<div>` | Fixed centering container; owns body scroll lock |
| `Dialog.Popup` | `<div>` | The dialog content container (`role="dialog"`, focus-trapped) |
| `Dialog.Title` | `<h2>` | Heading; wired to the popup's `aria-labelledby` |
| `Dialog.Description` | `<p>` | Description; wired to the popup's `aria-describedby` |
| `Dialog.Close` | `<button>` | Closes the dialog on click |

All rendered parts accept a `render` prop for polymorphic rendering and standard HTML
attributes for their default element. `Dialog.Title` and `Dialog.Description` manage their
own `id`. `Dialog.Portal` is optional β€” `Dialog.Backdrop` may be nested directly under
`Dialog.Root`.
attributes for their default element. Compound parts throw if used outside `Dialog.Root`.
`Dialog.Title` and `Dialog.Description` manage their own `id`. `Dialog.Portal` is optional;
for centered, scroll-locked modal behavior nest `Dialog.Popup` inside `Dialog.Viewport`.

## Props

Expand All@@ -81,41 +89,43 @@ own `id`. `Dialog.Portal` is optional β€” `Dialog.Backdrop` may be nested direct

### `Dialog.Portal`

| Prop | Type | Default | Description |
| ----------------- | ---------------------------------------------------------------- | -------------------------- | ---------------------------------------------------------------------------------------------------- |
| <code>root</code> | <code>HTMLElement \| null \| RefObject&lt;HTMLElement&gt;</code> | <code>document.body</code> | Container to portal into; non-null switches the backdrop to "scoped" mode (no overlay / scroll lock) |
| Prop | Type | Default | Description |
| ----------------- | ---------------------------------------------------------------- | -------------------------- | -------------------------------- |
| <code>root</code> | <code>HTMLElement \| null \| RefObject&lt;HTMLElement&gt;</code> | <code>document.body</code> | Container element to portal into |

### `Dialog.Backdrop`
### `Dialog.Viewport`

| Prop | Type | Default | Description |
| ----------------------- | -------------------- | ----------------- | ----------------------------------------- |
| <code>lockScroll</code> | <code>boolean</code> | <code>true</code> | Lock body scroll while the dialog is open |

`Dialog.Trigger`, `Dialog.Popup`, `Dialog.Title`, `Dialog.Description`, and `Dialog.Close`
take no additional props beyond standard HTML attributes for their default element.
`Dialog.Trigger`, `Dialog.Backdrop`, `Dialog.Popup`, `Dialog.Title`, `Dialog.Description`,
and `Dialog.Close` take no additional props beyond standard HTML attributes for their
default element.

## Styling

Each part emits `data-cl-*` attributes you can target with any CSS solution:
The headless parts don't emit `data-cl-slot` β€” slot identity is applied by the styled
(Mosaic) layer. Target a part with your own class (or `render` prop) and combine it with
the `data-cl-*` state attributes each part emits:

| Attribute | Applies To | Description |
| ------------------------ | ------------------------ | ------------------------------------------------- |
| `data-cl-slot` | All parts | Part identifier |
| `data-cl-open` | Trigger, Backdrop, Popup | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Popup | Present when closed (still mounted while exiting) |
| `data-cl-starting-style` | Backdrop, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Popup | Present during the exit animation |
| Attribute | Applies To | Description |
| ------------------------ | ---------------------------------- | ------------------------------------------------- |
| `data-cl-open` | Trigger, Backdrop, Viewport, Popup | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Viewport, Popup | Present when closed (still mounted while exiting) |
| `data-cl-starting-style` | Backdrop, Viewport, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Viewport, Popup | Present during the exit animation |

The backdropand popup stay mounted through the exit animation, so enter/exit transitions
are CSS-driven:
The backdrop, viewport, and popup stay mounted through the exit animation, so enter/exit
transitions are CSS-driven:

```css
[data-cl-slot='dialog-popup'] {
.dialog-popup {
opacity: 1;
transition: opacity 150ms ease;
}
[data-cl-slot='dialog-popup'][data-cl-starting-style],
[data-cl-slot='dialog-popup'][data-cl-ending-style] {
.dialog-popup[data-cl-starting-style],
.dialog-popup[data-cl-ending-style] {
opacity: 0;
}
```
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/bold-horses-rhyme.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
---
---
44 changes: 23 additions & 21 deletions packages/swingset/src/stories/dialog.component.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,13 +7,27 @@ primitives, composed with Mosaic slot recipes. It flattens the required nesting
Backdrop, Viewport, Popup) into a single component and exposes a `close` callback through a
render-prop children pattern.

## Example
## Playground

<Story
<Preview
name='Default'
storyModule={DialogStories}
/>

## Props

<PropTable
meta={DialogStories.meta}
extra={[
{ name: 'trigger', type: '(props: HTMLAttributes<HTMLElement>) => ReactElement' },
{ name: 'children', type: 'ReactNode | ((ctx: { close: () => void }) => ReactNode)' },
{ name: 'open', type: 'boolean' },
{ name: 'defaultOpen', type: 'boolean', default: 'false' },
{ name: 'onOpenChange', type: '(open: boolean) => void' },
{ name: 'modal', type: 'boolean', default: 'true' },
]}
Comment on lines +19 to +28

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor | ⚑ Quick win

Provide defaults for every prop in the generated props table input.

In extra, several props (trigger, children, open, onOpenChange) do not declare a default. Please set explicit defaults (typically β€”) so the rendered table stays compliant and unambiguous.

As per coding guidelines: β€œIn all props tables, document the default value for every prop in a dedicated Default column … use β€” when there is no default.”

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/swingset/src/stories/dialog.component.mdx` around lines 19 - 28, The
PropTable component in the dialog.component.mdx file has an extra array with
prop definitions where some props are missing the default field. Add a default
property to each prop object in the extra array that currently lacks one
(trigger, children, open, and onOpenChange). For props without a meaningful
default value, use "β€”" as the default to comply with the props table
documentation standards. Ensure all prop objects in the extra array now include
an explicit default field.

Source: Coding guidelines

/>

## Usage
Comment on lines +10 to 31

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚑ Quick win

Add the required Examples section for Archetype A docs.

This page currently has Playground β†’ Props β†’ Usage but omits Examples, which is required for Archetype A MDX docs.

As per coding guidelines: β€œFor Archetype A (simple CVA) MDX files, required sections in order: Playground, Props, Usage, then Examples. Never reorder or omit these sections.”

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/swingset/src/stories/dialog.component.mdx` around lines 10 - 31, The
dialog.component.mdx file is missing the required Examples section which is
mandatory for Archetype A documentation. The file currently contains Playground,
Props, and Usage sections, but according to guidelines, the Examples section
must be included after Usage. Add an Examples section after the Usage section
heading with relevant code examples demonstrating how to use the Dialog
component in different scenarios.

Source: Coding guidelines


```tsx
Expand DownExpand Up@@ -63,18 +77,6 @@ const [open, setOpen] = useState(false);
</Dialog>
```

## Props

| Prop | Type | Default | Description |
| -------------- | ---------------------------------------------------------- | ------- | ------------------------------------------------- |
| `trigger` | `(props: HTMLAttributes<HTMLElement>) => ReactElement` | β€” | Render prop for the trigger element |
| `children` | `ReactNode \| ((ctx: { close: () => void }) => ReactNode)` | β€” | Dialog content; use render prop to access `close` |
| `size` | `'md' \| 'lg'` | `'md'` | Controls the popup width |
| `open` | `boolean` | β€” | Controlled open state |
| `defaultOpen` | `boolean` | `false` | Initial open state (uncontrolled) |
| `onOpenChange` | `(open: boolean) => void` | β€” | Called when the open state changes |
| `modal` | `boolean` | `true` | Trap focus and make the rest of the page inert |

## Sub-parts

| Part | Slot | Description |
Expand All@@ -97,10 +99,10 @@ The Mosaic dialog exposes the following slots that can be styled via `appearance
Override per slot through `appearance.elements` β€” e.g. `{ 'dialog-popup': { borderRadius: 24 } }`.
State attributes from the headless layer are also available for CSS targeting:

| Attribute | Applies To | Description |
| ------------------------ | --------------------------- | --------------------------------- |
| `data-cl-slot` | All parts | Part identifier |
| `data-cl-open` | Trigger, Backdrop, Viewport | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Viewport | Present when closed (during exit) |
| `data-cl-starting-style` | Backdrop, Viewport, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Viewport, Popup | Present during the exit animation |
| Attribute | Applies To | Description |
| ------------------------ | ---------------------------------- | --------------------------------- |
| `data-cl-slot` | All parts | Part identifier |
| `data-cl-open` | Trigger, Backdrop, Viewport, Popup | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Viewport, Popup | Present when closed (during exit) |
| `data-cl-starting-style` | Backdrop, Viewport, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Viewport, Popup | Present during the exit animation |
84 changes: 47 additions & 37 deletions packages/swingset/src/stories/dialog.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,9 +4,9 @@ import * as DialogStories from './dialog.stories';

A modal window that overlays the page and traps focus until dismissed, from
`@clerk/headless`. It is a **headless** primitive: it supplies open state, portalling, an
Comment on lines 5 to 6

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor | ⚑ Quick win

Clarify focus-trap wording as modal-only behavior.

The intro and Dialog.Popup description read as unconditional focus trapping, but this page also documents modal={false} where focus is not trapped. Please qualify these lines as β€œin modal mode” (or β€œby default”).

Also applies to: 69-69

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/swingset/src/stories/dialog.mdx` around lines 5 - 6, The
documentation in the Dialog component description incorrectly presents focus
trapping as unconditional behavior, but the component supports a modal={false}
option where focus is not trapped. Locate the introductory description around
line 5-6 that states the component "traps focus until dismissed" and the
Dialog.Popup description around line 69, then add qualifying language such as
"in modal mode" or "by default" to both locations to clarify that focus trapping
only occurs when the modal prop is enabled. This ensures the documentation
accurately reflects that focus trapping is conditional behavior rather than a
default always-on feature.

overlay with optional scroll lock, focus management, dismissal (outside press / Escape),
and ARIA wiring, but ships **no styles** β€” you bring your own CSS by targeting the
`data-cl-*` attributes each part emits.
overlay surface, a centering viewport with optional body scroll lock, focus management,
dismissal (outside press / Escape), and ARIA wiring, but ships **no styles** β€” you bring
your own CSS by targeting the `data-cl-*` state attributes each part emits.

## Example

Expand All@@ -26,13 +26,14 @@ import { Dialog } from '@clerk/headless/dialog';
<Dialog.Root>
<Dialog.Trigger>Open</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Backdrop>
<Dialog.Backdrop />
<Dialog.Viewport>
<Dialog.Popup>
<Dialog.Title>Confirm action</Dialog.Title>
<Dialog.Description>Are you sure you want to proceed?</Dialog.Description>
<Dialog.Close>Cancel</Dialog.Close>
</Dialog.Popup>
</Dialog.Backdrop>
</Dialog.Viewport>
</Dialog.Portal>
</Dialog.Root>;
```
Expand All@@ -50,23 +51,30 @@ const [open, setOpen] = useState(false);
</Dialog.Root>;
```

### Non-modal (page stays interactive)

```tsx
<Dialog.Root modal={false}>{/* focus is not trapped and the rest of the page stays interactive */}</Dialog.Root>
```

## Parts

| Part | Default Element | Description |
| -------------------- | --------------- | --------------------------------------------------------------- |
| `Dialog.Root` | none (context) | Owns open state, ARIA ids, and the transition lifecycle |
| `Dialog.Trigger` | `<button>` | Toggles the dialog open on click |
| `Dialog.Portal` | none (portal) | Portals its children; renders nothing until mounted |
| `Dialog.Backdrop` | `<div>` | Overlay; hosts the fixed backdrop and optional body scroll lock |
| `Dialog.Popup` | `<div>` | The dialog content container (`role="dialog"`, focus-trapped) |
| `Dialog.Title` | `<h2>` | Heading; wired to the popup's `aria-labelledby` |
| `Dialog.Description` | `<p>` | Description; wired to the popup's `aria-describedby` |
| `Dialog.Close` | `<button>` | Closes the dialog on click |
| Part | Default Element | Description |
| -------------------- | --------------- | ------------------------------------------------------------------- |
| `Dialog.Root` | none (context) | Owns open state, ARIA ids, modal mode, and the transition lifecycle |
| `Dialog.Trigger` | `<button>` | Toggles the dialog open on click |
| `Dialog.Portal` | none (portal) | Portals its children; renders nothing until mounted |
| `Dialog.Backdrop` | `<div>` | Semi-transparent overlay surface behind the popup |
| `Dialog.Viewport` | `<div>` | Fixed centering container; owns body scroll lock |
| `Dialog.Popup` | `<div>` | The dialog content container (`role="dialog"`, focus-trapped) |
| `Dialog.Title` | `<h2>` | Heading; wired to the popup's `aria-labelledby` |
| `Dialog.Description` | `<p>` | Description; wired to the popup's `aria-describedby` |
| `Dialog.Close` | `<button>` | Closes the dialog on click |

All rendered parts accept a `render` prop for polymorphic rendering and standard HTML
attributes for their default element. `Dialog.Title` and `Dialog.Description` manage their
own `id`. `Dialog.Portal` is optional β€” `Dialog.Backdrop` may be nested directly under
`Dialog.Root`.
attributes for their default element. Compound parts throw if used outside `Dialog.Root`.
`Dialog.Title` and `Dialog.Description` manage their own `id`. `Dialog.Portal` is optional;
for centered, scroll-locked modal behavior nest `Dialog.Popup` inside `Dialog.Viewport`.

## Props

Expand All@@ -81,41 +89,43 @@ own `id`. `Dialog.Portal` is optional β€” `Dialog.Backdrop` may be nested direct

### `Dialog.Portal`

| Prop | Type | Default | Description |
| ----------------- | ---------------------------------------------------------------- | -------------------------- | ---------------------------------------------------------------------------------------------------- |
| <code>root</code> | <code>HTMLElement \| null \| RefObject&lt;HTMLElement&gt;</code> | <code>document.body</code> | Container to portal into; non-null switches the backdrop to "scoped" mode (no overlay / scroll lock) |
| Prop | Type | Default | Description |
| ----------------- | ---------------------------------------------------------------- | -------------------------- | -------------------------------- |
| <code>root</code> | <code>HTMLElement \| null \| RefObject&lt;HTMLElement&gt;</code> | <code>document.body</code> | Container element to portal into |

### `Dialog.Backdrop`
### `Dialog.Viewport`

| Prop | Type | Default | Description |
| ----------------------- | -------------------- | ----------------- | ----------------------------------------- |
| <code>lockScroll</code> | <code>boolean</code> | <code>true</code> | Lock body scroll while the dialog is open |

`Dialog.Trigger`, `Dialog.Popup`, `Dialog.Title`, `Dialog.Description`, and `Dialog.Close`
take no additional props beyond standard HTML attributes for their default element.
`Dialog.Trigger`, `Dialog.Backdrop`, `Dialog.Popup`, `Dialog.Title`, `Dialog.Description`,
and `Dialog.Close` take no additional props beyond standard HTML attributes for their
default element.

## Styling

Each part emits `data-cl-*` attributes you can target with any CSS solution:
The headless parts don't emit `data-cl-slot` β€” slot identity is applied by the styled
(Mosaic) layer. Target a part with your own class (or `render` prop) and combine it with
the `data-cl-*` state attributes each part emits:

| Attribute | Applies To | Description |
| ------------------------ | ------------------------ | ------------------------------------------------- |
| `data-cl-slot` | All parts | Part identifier |
| `data-cl-open` | Trigger, Backdrop, Popup | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Popup | Present when closed (still mounted while exiting) |
| `data-cl-starting-style` | Backdrop, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Popup | Present during the exit animation |
| Attribute | Applies To | Description |
| ------------------------ | ---------------------------------- | ------------------------------------------------- |
| `data-cl-open` | Trigger, Backdrop, Viewport, Popup | Present when the dialog is open |
| `data-cl-closed` | Trigger, Backdrop, Viewport, Popup | Present when closed (still mounted while exiting) |
| `data-cl-starting-style` | Backdrop, Viewport, Popup | Present on the entering frame |
| `data-cl-ending-style` | Backdrop, Viewport, Popup | Present during the exit animation |

The backdropand popup stay mounted through the exit animation, so enter/exit transitions
are CSS-driven:
The backdrop, viewport, and popup stay mounted through the exit animation, so enter/exit
transitions are CSS-driven:

```css
[data-cl-slot='dialog-popup'] {
.dialog-popup {
opacity: 1;
transition: opacity 150ms ease;
}
[data-cl-slot='dialog-popup'][data-cl-starting-style],
[data-cl-slot='dialog-popup'][data-cl-ending-style] {
.dialog-popup[data-cl-starting-style],
.dialog-popup[data-cl-ending-style] {
opacity: 0;
}
```
Loading