close
Skip to content

ui/Dialog, ui/AlertDialog, ui/Drawer: support sticky header and footer#77559

Merged
ciampo merged 41 commits into
trunkfrom
add/dialog-sticky-chrome
Apr 27, 2026
Merged

ui/Dialog, ui/AlertDialog, ui/Drawer: support sticky header and footer#77559
ciampo merged 41 commits into
trunkfrom
add/dialog-sticky-chrome

Conversation

@ciampo
Copy link
Copy Markdown
Contributor

@ciampo ciampo commented Apr 22, 2026

Closes #77180 · Supersedes #77182

Partially extracted to:

What?

Pin the header and footer of Dialog, AlertDialog, and Drawer to the popup edges when the body overflows, with a separator that appears only while there's off-screen content in that direction.

  • Dialog / Drawer: a new required Content subcomponent owns the scroll region. Render Header / Footer as siblings of Content to pin them; nest them inside Content to scroll with the body.
  • AlertDialog.Popup: gains stickyHeader / stickyFooter props (default true) for the same choice on its internal chrome.

Why?

When an overlay overflows, the title and primary actions scroll out of view. Pinning them keeps users oriented; conditional separators make overflow discoverable without permanent visual noise. Confining scroll to the body region also tucks the scrollbar between the chrome edges (cleaner, and unambiguous about what scrolls).

How?

A new shared utils/css/overlay-chrome.module.css centralizes the chrome layout and scroll-state separator styling; Dialog, AlertDialog, and Drawer all compose from it. The Popup is a flex column, and the Content element (Dialog / Drawer) or an internal scroll container (AlertDialog) owns the overflow — pinning comes for free from flex positioning, no position: sticky involved. Scrolling is block-axis only (overflow-inline: hidden); consumers should constrain content width rather than rely on horizontal scroll.

A companion hook useOverlayScrollStateAttributes marks the scroll container and toggles data-wp-ui-overlay-scrolled-from-{top,bottom} on it as the user scrolls, so CSS can draw the separators without React re-renders. The same hook conditionally toggles tabindex="0" on the scroll container only while it actually overflows, so keyboard users can focus and arrow-scroll the body (WCAG 2.1.1) without introducing a stray tab stop on non-scrolling popups. Dialog.Content / Drawer.Content expose tabIndex as an explicit prop with JSDoc covering the managed behavior and consumer-override edge case. useDeprioritizedInitialFocus keeps the newly tabbable container from stealing initial focus when real controls are available.

The pinned-chrome separator uses a transparent → token-color toggle, which forced-colors mode substitutes with CanvasText automatically.

Testing Instructions
  1. npm install
  2. cd packages/ui && npm run storybook
  3. Open the Scrollable story for each of Dialog, AlertDialog, and Drawer.
  4. Scroll the body — chrome stays pinned, and the separator appears only when content overflows in that direction.
  5. On Dialog / Drawer, toggle the story controls to move Header / Footer between sibling-of-Content (pinned) and nested-inside-Content (scrolls with body). On AlertDialog, toggle stickyHeader / stickyFooter.
  6. For Drawer, cycle through all four swipeDirections; swipe-to-dismiss should still engage once scroll reaches the edge.
  7. Enable forced-colors emulation in DevTools and reopen any overlay — the pinned separator should appear in the system text color once scrolled.

Keyboard

  1. Tab to the trigger, Enter to open.
  2. Tab through the overlay — action buttons stay reachable with a visible focus ring. If the body overflows, the scroll container is also in the tab order (PageDown / PageUp / arrows work); if it doesn't overflow, it isn't.
  3. Escape closes and returns focus to the trigger.

Follow-ups

  • Tidy / prune the new test suites now that the behavior has shaken out.

Screenshots or screencast

Before After
No sticky behavior on trunk
Kapture.2026-04-27.at.18.13.07.mp4

Use of AI Tools

Cursor + Claude Opus 4.7.

Copy link
Copy Markdown
Contributor Author

@ciampo ciampo Apr 22, 2026

Choose a reason for hiding this comment

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

Some of the changes in this file are not directly related to sticky header/footer, but are part of a refactor aimed at tidying up spacing (especially vertical), which was important when testing and comparing changes in this branch vs trunk

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 22, 2026

Size Change: -1 B (0%)

Total Size: 7.77 MB

📦 View Changed
Filename Size Change
build/modules/boot/index.min.js 17.2 kB -2 B (-0.01%)
build/scripts/edit-post/index.min.js 18.4 kB +1 B (+0.01%)
ℹ️ View Unchanged
Filename Size
build/modules/a11y/index.min.js 355 B
build/modules/abilities/index.min.js 42.3 kB
build/modules/block-editor/utils/fit-text-frontend.min.js 617 B
build/modules/block-library/accordion/view.min.js 595 B
build/modules/block-library/file/view.min.js 346 B
build/modules/block-library/form/view.min.js 528 B
build/modules/block-library/image/view.min.js 2.64 kB
build/modules/block-library/navigation/view.min.js 1.14 kB
build/modules/block-library/playlist/view.min.js 10.9 kB
build/modules/block-library/query/view.min.js 518 B
build/modules/block-library/search/view.min.js 498 B
build/modules/block-library/tabs/view.min.js 946 B
build/modules/connectors/index.min.js 2.05 kB
build/modules/core-abilities/index.min.js 907 B
build/modules/edit-site-init/index.min.js 1.4 kB
build/modules/interactivity-router/full-page.min.js 451 B
build/modules/interactivity-router/index.min.js 11.6 kB
build/modules/interactivity/index.min.js 15.1 kB
build/modules/latex-to-mathml/index.min.js 56.5 kB
build/modules/latex-to-mathml/loader.min.js 131 B
build/modules/lazy-editor/index.min.js 13.9 kB
build/modules/route/index.min.js 25.2 kB
build/modules/vips/loader.min.js 127 B
build/modules/vips/worker.min.js 4.56 MB
build/modules/workflow/index.min.js 19.9 kB
build/scripts/a11y/index.min.js 1.06 kB
build/scripts/annotations/index.min.js 2.49 kB
build/scripts/api-fetch/index.min.js 2.83 kB
build/scripts/autop/index.min.js 2.18 kB
build/scripts/base-styles/index.min.js 98 B
build/scripts/blob/index.min.js 631 B
build/scripts/block-directory/index.min.js 9.98 kB
build/scripts/block-editor/index.min.js 341 kB
build/scripts/block-library/index.min.js 319 kB
build/scripts/block-serialization-default-parser/index.min.js 1.16 kB
build/scripts/block-serialization-spec-parser/index.min.js 3.08 kB
build/scripts/blocks/index.min.js 56.9 kB
build/scripts/commands/index.min.js 21 kB
build/scripts/components/index.min.js 265 kB
build/scripts/compose/index.min.js 11.1 kB
build/scripts/core-commands/index.min.js 4.31 kB
build/scripts/core-data/index.min.js 30.9 kB
build/scripts/customize-widgets/index.min.js 14.3 kB
build/scripts/data-controls/index.min.js 795 B
build/scripts/data/index.min.js 9.66 kB
build/scripts/date/index.min.js 23.6 kB
build/scripts/deprecated/index.min.js 756 B
build/scripts/dom-ready/index.min.js 476 B
build/scripts/dom/index.min.js 5 kB
build/scripts/edit-site/index.min.js 264 kB
build/scripts/edit-widgets/index.min.js 21.9 kB
build/scripts/editor/index.min.js 422 kB
build/scripts/element/index.min.js 5.17 kB
build/scripts/escape-html/index.min.js 587 B
build/scripts/format-library/index.min.js 12.8 kB
build/scripts/hooks/index.min.js 1.83 kB
build/scripts/html-entities/index.min.js 494 B
build/scripts/i18n/index.min.js 2.47 kB
build/scripts/is-shallow-equal/index.min.js 572 B
build/scripts/keyboard-shortcuts/index.min.js 1.61 kB
build/scripts/keycodes/index.min.js 1.56 kB
build/scripts/list-reusable-blocks/index.min.js 2.49 kB
build/scripts/media-utils/index.min.js 79.4 kB
build/scripts/notices/index.min.js 1.85 kB
build/scripts/nux/index.min.js 1.89 kB
build/scripts/patterns/index.min.js 7.96 kB
build/scripts/plugins/index.min.js 2.15 kB
build/scripts/preferences-persistence/index.min.js 2.15 kB
build/scripts/preferences/index.min.js 3.3 kB
build/scripts/primitives/index.min.js 1.01 kB
build/scripts/priority-queue/index.min.js 1.62 kB
build/scripts/private-apis/index.min.js 1.1 kB
build/scripts/react-i18n/index.min.js 833 B
build/scripts/redux-routine/index.min.js 3.37 kB
build/scripts/reusable-blocks/index.min.js 3.1 kB
build/scripts/rich-text/index.min.js 14 kB
build/scripts/router/index.min.js 5.96 kB
build/scripts/server-side-render/index.min.js 1.91 kB
build/scripts/shortcode/index.min.js 1.59 kB
build/scripts/style-engine/index.min.js 2.42 kB
build/scripts/sync/index.min.js 38.8 kB
build/scripts/theme/index.min.js 22 kB
build/scripts/token-list/index.min.js 739 B
build/scripts/undo-manager/index.min.js 918 B
build/scripts/upload-media/index.min.js 11.2 kB
build/scripts/url/index.min.js 3.98 kB
build/scripts/vendors/react-dom.min.js 43.3 kB
build/scripts/vendors/react-jsx-runtime.min.js 667 B
build/scripts/vendors/react.min.js 2.77 kB
build/scripts/viewport/index.min.js 1.22 kB
build/scripts/warning/index.min.js 454 B
build/scripts/widgets/index.min.js 7.8 kB
build/scripts/wordcount/index.min.js 1.04 kB
build/styles/base-styles/admin-schemes-rtl.css 1.71 kB
build/styles/base-styles/admin-schemes-rtl.min.css 775 B
build/styles/base-styles/admin-schemes.css 1.71 kB
build/styles/base-styles/admin-schemes.min.css 775 B
build/styles/block-directory/style-rtl.css 1.97 kB
build/styles/block-directory/style-rtl.min.css 1.06 kB
build/styles/block-directory/style.css 1.98 kB
build/styles/block-directory/style.min.css 1.06 kB
build/styles/block-editor/content-rtl.css 5.46 kB
build/styles/block-editor/content-rtl.min.css 4.03 kB
build/styles/block-editor/content.css 5.46 kB
build/styles/block-editor/content.min.css 4.02 kB
build/styles/block-editor/default-editor-styles-rtl.css 697 B
build/styles/block-editor/default-editor-styles-rtl.min.css 224 B
build/styles/block-editor/default-editor-styles.css 697 B
build/styles/block-editor/default-editor-styles.min.css 224 B
build/styles/block-editor/style-rtl.css 18.6 kB
build/styles/block-editor/style-rtl.min.css 15.8 kB
build/styles/block-editor/style.css 18.6 kB
build/styles/block-editor/style.min.css 15.8 kB
build/styles/block-library/accordion-heading/style-rtl.css 346 B
build/styles/block-library/accordion-heading/style-rtl.min.css 325 B
build/styles/block-library/accordion-heading/style.css 346 B
build/styles/block-library/accordion-heading/style.min.css 325 B
build/styles/block-library/accordion-item/style-rtl.css 239 B
build/styles/block-library/accordion-item/style-rtl.min.css 180 B
build/styles/block-library/accordion-item/style.css 238 B
build/styles/block-library/accordion-item/style.min.css 180 B
build/styles/block-library/accordion-panel/style-rtl.css 110 B
build/styles/block-library/accordion-panel/style-rtl.min.css 99 B
build/styles/block-library/accordion-panel/style.css 110 B
build/styles/block-library/accordion-panel/style.min.css 99 B
build/styles/block-library/accordion/style-rtl.css 69 B
build/styles/block-library/accordion/style-rtl.min.css 62 B
build/styles/block-library/accordion/style.css 69 B
build/styles/block-library/accordion/style.min.css 62 B
build/styles/block-library/archives/style-rtl.css 101 B
build/styles/block-library/archives/style-rtl.min.css 90 B
build/styles/block-library/archives/style.css 101 B
build/styles/block-library/archives/style.min.css 90 B
build/styles/block-library/audio/editor-rtl.css 166 B
build/styles/block-library/audio/editor-rtl.min.css 149 B
build/styles/block-library/audio/editor.css 166 B
build/styles/block-library/audio/editor.min.css 151 B
build/styles/block-library/audio/style-rtl.css 945 B
build/styles/block-library/audio/style-rtl.min.css 132 B
build/styles/block-library/audio/style.css 945 B
build/styles/block-library/audio/style.min.css 132 B
build/styles/block-library/audio/theme-rtl.css 967 B
build/styles/block-library/audio/theme-rtl.min.css 134 B
build/styles/block-library/audio/theme.css 967 B
build/styles/block-library/audio/theme.min.css 134 B
build/styles/block-library/avatar/editor-rtl.css 127 B
build/styles/block-library/avatar/editor-rtl.min.css 115 B
build/styles/block-library/avatar/editor.css 127 B
build/styles/block-library/avatar/editor.min.css 115 B
build/styles/block-library/avatar/style-rtl.css 117 B
build/styles/block-library/avatar/style-rtl.min.css 104 B
build/styles/block-library/avatar/style.css 117 B
build/styles/block-library/avatar/style.min.css 104 B
build/styles/block-library/breadcrumbs/style-rtl.css 233 B
build/styles/block-library/breadcrumbs/style-rtl.min.css 203 B
build/styles/block-library/breadcrumbs/style.css 233 B
build/styles/block-library/breadcrumbs/style.min.css 203 B
build/styles/block-library/button/editor-rtl.css 306 B
build/styles/block-library/button/editor-rtl.min.css 265 B
build/styles/block-library/button/editor.css 317 B
build/styles/block-library/button/editor.min.css 265 B
build/styles/block-library/button/style-rtl.css 651 B
build/styles/block-library/button/style-rtl.min.css 596 B
build/styles/block-library/button/style.css 662 B
build/styles/block-library/button/style.min.css 596 B
build/styles/block-library/buttons/editor-rtl.css 391 B
build/styles/block-library/buttons/editor-rtl.min.css 291 B
build/styles/block-library/buttons/editor.css 391 B
build/styles/block-library/buttons/editor.min.css 291 B
build/styles/block-library/buttons/style-rtl.css 452 B
build/styles/block-library/buttons/style-rtl.min.css 349 B
build/styles/block-library/buttons/style.css 453 B
build/styles/block-library/buttons/style.min.css 349 B
build/styles/block-library/calendar/style-rtl.css 271 B
build/styles/block-library/calendar/style-rtl.min.css 239 B
build/styles/block-library/calendar/style.css 271 B
build/styles/block-library/calendar/style.min.css 239 B
build/styles/block-library/categories/editor-rtl.css 171 B
build/styles/block-library/categories/editor-rtl.min.css 132 B
build/styles/block-library/categories/editor.css 170 B
build/styles/block-library/categories/editor.min.css 131 B
build/styles/block-library/categories/style-rtl.css 226 B
build/styles/block-library/categories/style-rtl.min.css 169 B
build/styles/block-library/categories/style.css 235 B
build/styles/block-library/categories/style.min.css 169 B
build/styles/block-library/classic-rtl.css 363 B
build/styles/block-library/classic-rtl.min.css 321 B
build/styles/block-library/classic.css 363 B
build/styles/block-library/classic.min.css 321 B
build/styles/block-library/code/editor-rtl.css 59 B
build/styles/block-library/code/editor-rtl.min.css 53 B
build/styles/block-library/code/editor.css 59 B
build/styles/block-library/code/editor.min.css 53 B
build/styles/block-library/code/style-rtl.css 158 B
build/styles/block-library/code/style-rtl.min.css 140 B
build/styles/block-library/code/style.css 178 B
build/styles/block-library/code/style.min.css 140 B
build/styles/block-library/code/theme-rtl.css 135 B
build/styles/block-library/code/theme-rtl.min.css 122 B
build/styles/block-library/code/theme.css 135 B
build/styles/block-library/code/theme.min.css 122 B
build/styles/block-library/columns/editor-rtl.css 119 B
build/styles/block-library/columns/editor-rtl.min.css 108 B
build/styles/block-library/columns/editor.css 119 B
build/styles/block-library/columns/editor.min.css 108 B
build/styles/block-library/columns/style-rtl.css 1.3 kB
build/styles/block-library/columns/style-rtl.min.css 421 B
build/styles/block-library/columns/style.css 1.3 kB
build/styles/block-library/columns/style.min.css 421 B
build/styles/block-library/comment-author-avatar/editor-rtl.css 136 B
build/styles/block-library/comment-author-avatar/editor-rtl.min.css 124 B
build/styles/block-library/comment-author-avatar/editor.css 136 B
build/styles/block-library/comment-author-avatar/editor.min.css 124 B
build/styles/block-library/comment-author-name/style-rtl.css 79 B
build/styles/block-library/comment-author-name/style-rtl.min.css 72 B
build/styles/block-library/comment-author-name/style.css 79 B
build/styles/block-library/comment-author-name/style.min.css 72 B
build/styles/block-library/comment-content/style-rtl.css 137 B
build/styles/block-library/comment-content/style-rtl.min.css 120 B
build/styles/block-library/comment-content/style.css 137 B
build/styles/block-library/comment-content/style.min.css 120 B
build/styles/block-library/comment-date/style-rtl.css 72 B
build/styles/block-library/comment-date/style-rtl.min.css 65 B
build/styles/block-library/comment-date/style.css 72 B
build/styles/block-library/comment-date/style.min.css 65 B
build/styles/block-library/comment-edit-link/style-rtl.css 77 B
build/styles/block-library/comment-edit-link/style-rtl.min.css 70 B
build/styles/block-library/comment-edit-link/style.css 77 B
build/styles/block-library/comment-edit-link/style.min.css 70 B
build/styles/block-library/comment-reply-link/style-rtl.css 78 B
build/styles/block-library/comment-reply-link/style-rtl.min.css 71 B
build/styles/block-library/comment-reply-link/style.css 78 B
build/styles/block-library/comment-reply-link/style.min.css 71 B
build/styles/block-library/comment-template/style-rtl.css 213 B
build/styles/block-library/comment-template/style-rtl.min.css 191 B
build/styles/block-library/comment-template/style.css 213 B
build/styles/block-library/comment-template/style.min.css 191 B
build/styles/block-library/comments-pagination-numbers/editor-rtl.css 135 B
build/styles/block-library/comments-pagination-numbers/editor-rtl.min.css 122 B
build/styles/block-library/comments-pagination-numbers/editor.css 144 B
build/styles/block-library/comments-pagination-numbers/editor.min.css 121 B
build/styles/block-library/comments-pagination/editor-rtl.css 184 B
build/styles/block-library/comments-pagination/editor-rtl.min.css 168 B
build/styles/block-library/comments-pagination/editor.css 184 B
build/styles/block-library/comments-pagination/editor.min.css 168 B
build/styles/block-library/comments-pagination/style-rtl.css 224 B
build/styles/block-library/comments-pagination/style-rtl.min.css 201 B
build/styles/block-library/comments-pagination/style.css 236 B
build/styles/block-library/comments-pagination/style.min.css 201 B
build/styles/block-library/comments-title/editor-rtl.css 83 B
build/styles/block-library/comments-title/editor-rtl.min.css 75 B
build/styles/block-library/comments-title/editor.css 83 B
build/styles/block-library/comments-title/editor.min.css 75 B
build/styles/block-library/comments/editor-rtl.css 968 B
build/styles/block-library/comments/editor-rtl.min.css 842 B
build/styles/block-library/comments/editor.css 968 B
build/styles/block-library/comments/editor.min.css 842 B
build/styles/block-library/comments/style-rtl.css 754 B
build/styles/block-library/comments/style-rtl.min.css 637 B
build/styles/block-library/comments/style.css 752 B
build/styles/block-library/comments/style.min.css 637 B
build/styles/block-library/common-rtl.css 2.48 kB
build/styles/block-library/common-rtl.min.css 1.12 kB
build/styles/block-library/common.css 2.5 kB
build/styles/block-library/common.min.css 1.12 kB
build/styles/block-library/cover/editor-rtl.css 1.05 kB
build/styles/block-library/cover/editor-rtl.min.css 631 B
build/styles/block-library/cover/editor.css 1.05 kB
build/styles/block-library/cover/editor.min.css 631 B
build/styles/block-library/cover/style-rtl.css 2.5 kB
build/styles/block-library/cover/style-rtl.min.css 1.82 kB
build/styles/block-library/cover/style.css 2.51 kB
build/styles/block-library/cover/style.min.css 1.81 kB
build/styles/block-library/details/editor-rtl.css 72 B
build/styles/block-library/details/editor-rtl.min.css 65 B
build/styles/block-library/details/editor.css 72 B
build/styles/block-library/details/editor.min.css 65 B
build/styles/block-library/details/style-rtl.css 97 B
build/styles/block-library/details/style-rtl.min.css 86 B
build/styles/block-library/details/style.css 97 B
build/styles/block-library/details/style.min.css 86 B
build/styles/block-library/editor-elements-rtl.css 117 B
build/styles/block-library/editor-elements-rtl.min.css 75 B
build/styles/block-library/editor-elements.css 117 B
build/styles/block-library/editor-elements.min.css 75 B
build/styles/block-library/editor-rtl.css 12.5 kB
build/styles/block-library/editor-rtl.min.css 10.3 kB
build/styles/block-library/editor.css 12.5 kB
build/styles/block-library/editor.min.css 10.3 kB
build/styles/block-library/elements-rtl.css 84 B
build/styles/block-library/elements-rtl.min.css 54 B
build/styles/block-library/elements.css 84 B
build/styles/block-library/elements.min.css 54 B
build/styles/block-library/embed/editor-rtl.css 391 B
build/styles/block-library/embed/editor-rtl.min.css 331 B
build/styles/block-library/embed/editor.css 390 B
build/styles/block-library/embed/editor.min.css 331 B
build/styles/block-library/embed/style-rtl.css 1.29 kB
build/styles/block-library/embed/style-rtl.min.css 448 B
build/styles/block-library/embed/style.css 1.29 kB
build/styles/block-library/embed/style.min.css 448 B
build/styles/block-library/embed/theme-rtl.css 967 B
build/styles/block-library/embed/theme-rtl.min.css 133 B
build/styles/block-library/embed/theme.css 967 B
build/styles/block-library/embed/theme.min.css 133 B
build/styles/block-library/file/editor-rtl.css 352 B
build/styles/block-library/file/editor-rtl.min.css 324 B
build/styles/block-library/file/editor.css 353 B
build/styles/block-library/file/editor.min.css 324 B
build/styles/block-library/file/style-rtl.css 318 B
build/styles/block-library/file/style-rtl.min.css 278 B
build/styles/block-library/file/style.css 331 B
build/styles/block-library/file/style.min.css 278 B
build/styles/block-library/footnotes/style-rtl.css 220 B
build/styles/block-library/footnotes/style-rtl.min.css 198 B
build/styles/block-library/footnotes/style.css 219 B
build/styles/block-library/footnotes/style.min.css 197 B
build/styles/block-library/form-input/editor-rtl.css 286 B
build/styles/block-library/form-input/editor-rtl.min.css 265 B
build/styles/block-library/form-input/editor.css 285 B
build/styles/block-library/form-input/editor.min.css 264 B
build/styles/block-library/form-input/style-rtl.css 467 B
build/styles/block-library/form-input/style-rtl.min.css 366 B
build/styles/block-library/form-input/style.css 467 B
build/styles/block-library/form-input/style.min.css 366 B
build/styles/block-library/form-submission-notification/editor-rtl.css 368 B
build/styles/block-library/form-submission-notification/editor-rtl.min.css 344 B
build/styles/block-library/form-submission-notification/editor.css 368 B
build/styles/block-library/form-submission-notification/editor.min.css 341 B
build/styles/block-library/form-submit-button/style-rtl.css 77 B
build/styles/block-library/form-submit-button/style-rtl.min.css 69 B
build/styles/block-library/form-submit-button/style.css 77 B
build/styles/block-library/form-submit-button/style.min.css 69 B
build/styles/block-library/freeform/editor-rtl.css 1.12 kB
build/styles/block-library/freeform/editor-rtl.min.css 288 B
build/styles/block-library/freeform/editor.css 1.12 kB
build/styles/block-library/freeform/editor.min.css 288 B
build/styles/block-library/gallery/editor-rtl.css 1.52 kB
build/styles/block-library/gallery/editor-rtl.min.css 615 B
build/styles/block-library/gallery/editor.css 1.52 kB
build/styles/block-library/gallery/editor.min.css 616 B
build/styles/block-library/gallery/style-rtl.css 2.84 kB
build/styles/block-library/gallery/style-rtl.min.css 1.84 kB
build/styles/block-library/gallery/style.css 2.84 kB
build/styles/block-library/gallery/style.min.css 1.84 kB
build/styles/block-library/gallery/theme-rtl.css 941 B
build/styles/block-library/gallery/theme-rtl.min.css 108 B
build/styles/block-library/gallery/theme.css 941 B
build/styles/block-library/gallery/theme.min.css 108 B
build/styles/block-library/group/editor-rtl.css 772 B
build/styles/block-library/group/editor-rtl.min.css 335 B
build/styles/block-library/group/editor.css 772 B
build/styles/block-library/group/editor.min.css 335 B
build/styles/block-library/group/style-rtl.css 120 B
build/styles/block-library/group/style-rtl.min.css 103 B
build/styles/block-library/group/style.css 120 B
build/styles/block-library/group/style.min.css 103 B
build/styles/block-library/group/theme-rtl.css 468 B
build/styles/block-library/group/theme-rtl.min.css 79 B
build/styles/block-library/group/theme.css 468 B
build/styles/block-library/group/theme.min.css 79 B
build/styles/block-library/heading/style-rtl.css 604 B
build/styles/block-library/heading/style-rtl.min.css 205 B
build/styles/block-library/heading/style.css 604 B
build/styles/block-library/heading/style.min.css 205 B
build/styles/block-library/html/editor-rtl.css 1.29 kB
build/styles/block-library/html/editor-rtl.min.css 464 B
build/styles/block-library/html/editor.css 1.3 kB
build/styles/block-library/html/editor.min.css 464 B
build/styles/block-library/icon/editor-rtl.css 776 B
build/styles/block-library/icon/editor-rtl.min.css 377 B
build/styles/block-library/icon/editor.css 776 B
build/styles/block-library/icon/editor.min.css 377 B
build/styles/block-library/icon/style-rtl.css 218 B
build/styles/block-library/icon/style-rtl.min.css 154 B
build/styles/block-library/icon/style.css 218 B
build/styles/block-library/icon/style.min.css 154 B
build/styles/block-library/image/editor-rtl.css 1.64 kB
build/styles/block-library/image/editor-rtl.min.css 782 B
build/styles/block-library/image/editor.css 1.64 kB
build/styles/block-library/image/editor.min.css 780 B
build/styles/block-library/image/style-rtl.css 2.92 kB
build/styles/block-library/image/style-rtl.min.css 1.86 kB
build/styles/block-library/image/style.css 2.92 kB
build/styles/block-library/image/style.min.css 1.85 kB
build/styles/block-library/image/theme-rtl.css 971 B
build/styles/block-library/image/theme-rtl.min.css 137 B
build/styles/block-library/image/theme.css 971 B
build/styles/block-library/image/theme.min.css 137 B
build/styles/block-library/latest-comments/style-rtl.css 392 B
build/styles/block-library/latest-comments/style-rtl.min.css 352 B
build/styles/block-library/latest-comments/style.css 390 B
build/styles/block-library/latest-comments/style.min.css 352 B
build/styles/block-library/latest-posts/editor-rtl.css 154 B
build/styles/block-library/latest-posts/editor-rtl.min.css 139 B
build/styles/block-library/latest-posts/editor.css 153 B
build/styles/block-library/latest-posts/editor.min.css 138 B
build/styles/block-library/latest-posts/style-rtl.css 1.36 kB
build/styles/block-library/latest-posts/style-rtl.min.css 520 B
build/styles/block-library/latest-posts/style.css 1.37 kB
build/styles/block-library/latest-posts/style.min.css 520 B
build/styles/block-library/list/style-rtl.css 498 B
build/styles/block-library/list/style-rtl.min.css 107 B
build/styles/block-library/list/style.css 498 B
build/styles/block-library/list/style.min.css 107 B
build/styles/block-library/loginout/style-rtl.css 68 B
build/styles/block-library/loginout/style-rtl.min.css 61 B
build/styles/block-library/loginout/style.css 68 B
build/styles/block-library/loginout/style.min.css 61 B
build/styles/block-library/math/editor-rtl.css 491 B
build/styles/block-library/math/editor-rtl.min.css 105 B
build/styles/block-library/math/editor.css 502 B
build/styles/block-library/math/editor.min.css 105 B
build/styles/block-library/math/style-rtl.css 70 B
build/styles/block-library/math/style-rtl.min.css 61 B
build/styles/block-library/math/style.css 70 B
build/styles/block-library/math/style.min.css 61 B
build/styles/block-library/media-text/editor-rtl.css 389 B
build/styles/block-library/media-text/editor-rtl.min.css 321 B
build/styles/block-library/media-text/editor.css 389 B
build/styles/block-library/media-text/editor.min.css 320 B
build/styles/block-library/media-text/style-rtl.css 873 B
build/styles/block-library/media-text/style-rtl.min.css 552 B
build/styles/block-library/media-text/style.css 901 B
build/styles/block-library/media-text/style.min.css 550 B
build/styles/block-library/more/editor-rtl.css 796 B
build/styles/block-library/more/editor-rtl.min.css 393 B
build/styles/block-library/more/editor.css 798 B
build/styles/block-library/more/editor.min.css 393 B
build/styles/block-library/navigation-link/editor-rtl.css 1.28 kB
build/styles/block-library/navigation-link/editor-rtl.min.css 710 B
build/styles/block-library/navigation-link/editor.css 1.27 kB
build/styles/block-library/navigation-link/editor.min.css 713 B
build/styles/block-library/navigation-link/style-rtl.css 579 B
build/styles/block-library/navigation-link/style-rtl.min.css 190 B
build/styles/block-library/navigation-link/style.css 579 B
build/styles/block-library/navigation-link/style.min.css 188 B
build/styles/block-library/navigation-overlay-close/style-rtl.css 260 B
build/styles/block-library/navigation-overlay-close/style-rtl.min.css 237 B
build/styles/block-library/navigation-overlay-close/style.css 260 B
build/styles/block-library/navigation-overlay-close/style.min.css 237 B
build/styles/block-library/navigation-submenu/editor-rtl.css 1.12 kB
build/styles/block-library/navigation-submenu/editor-rtl.min.css 295 B
build/styles/block-library/navigation-submenu/editor.css 1.12 kB
build/styles/block-library/navigation-submenu/editor.min.css 294 B
build/styles/block-library/navigation/editor-rtl.css 3.28 kB
build/styles/block-library/navigation/editor-rtl.min.css 2.28 kB
build/styles/block-library/navigation/editor.css 3.29 kB
build/styles/block-library/navigation/editor.min.css 2.28 kB
build/styles/block-library/navigation/style-rtl.css 3.59 kB
build/styles/block-library/navigation/style-rtl.min.css 2.52 kB
build/styles/block-library/navigation/style.css 3.59 kB
build/styles/block-library/navigation/style.min.css 2.5 kB
build/styles/block-library/nextpage/editor-rtl.css 799 B
build/styles/block-library/nextpage/editor-rtl.min.css 392 B
build/styles/block-library/nextpage/editor.css 800 B
build/styles/block-library/nextpage/editor.min.css 392 B
build/styles/block-library/page-list/editor-rtl.css 1.18 kB
build/styles/block-library/page-list/editor-rtl.min.css 356 B
build/styles/block-library/page-list/editor.css 1.18 kB
build/styles/block-library/page-list/editor.min.css 356 B
build/styles/block-library/page-list/style-rtl.css 207 B
build/styles/block-library/page-list/style-rtl.min.css 192 B
build/styles/block-library/page-list/style.css 207 B
build/styles/block-library/page-list/style.min.css 192 B
build/styles/block-library/paragraph/editor-rtl.css 315 B
build/styles/block-library/paragraph/editor-rtl.min.css 292 B
build/styles/block-library/paragraph/editor.css 314 B
build/styles/block-library/paragraph/editor.min.css 292 B
build/styles/block-library/paragraph/style-rtl.css 746 B
build/styles/block-library/paragraph/style-rtl.min.css 341 B
build/styles/block-library/paragraph/style.css 752 B
build/styles/block-library/paragraph/style.min.css 340 B
build/styles/block-library/playlist-track/style-rtl.css 453 B
build/styles/block-library/playlist-track/style-rtl.min.css 420 B
build/styles/block-library/playlist-track/style.css 453 B
build/styles/block-library/playlist-track/style.min.css 420 B
build/styles/block-library/playlist/editor-rtl.css 120 B
build/styles/block-library/playlist/editor-rtl.min.css 112 B
build/styles/block-library/playlist/editor.css 120 B
build/styles/block-library/playlist/editor.min.css 112 B
build/styles/block-library/playlist/style-rtl.css 1.52 kB
build/styles/block-library/playlist/style-rtl.min.css 1.42 kB
build/styles/block-library/playlist/style.css 1.52 kB
build/styles/block-library/playlist/style.min.css 1.42 kB
build/styles/block-library/post-author-biography/style-rtl.css 96 B
build/styles/block-library/post-author-biography/style-rtl.min.css 86 B
build/styles/block-library/post-author-biography/style.css 96 B
build/styles/block-library/post-author-biography/style.min.css 86 B
build/styles/block-library/post-author-name/style-rtl.css 76 B
build/styles/block-library/post-author-name/style-rtl.min.css 69 B
build/styles/block-library/post-author-name/style.css 76 B
build/styles/block-library/post-author-name/style.min.css 69 B
build/styles/block-library/post-author/editor-rtl.css 490 B
build/styles/block-library/post-author/editor-rtl.min.css 104 B
build/styles/block-library/post-author/editor.css 490 B
build/styles/block-library/post-author/editor.min.css 104 B
build/styles/block-library/post-author/style-rtl.css 213 B
build/styles/block-library/post-author/style-rtl.min.css 188 B
build/styles/block-library/post-author/style.css 214 B
build/styles/block-library/post-author/style.min.css 189 B
build/styles/block-library/post-comments-count/style-rtl.css 79 B
build/styles/block-library/post-comments-count/style-rtl.min.css 72 B
build/styles/block-library/post-comments-count/style.css 79 B
build/styles/block-library/post-comments-count/style.min.css 72 B
build/styles/block-library/post-comments-form/editor-rtl.css 104 B
build/styles/block-library/post-comments-form/editor-rtl.min.css 96 B
build/styles/block-library/post-comments-form/editor.css 104 B
build/styles/block-library/post-comments-form/editor.min.css 96 B
build/styles/block-library/post-comments-form/style-rtl.css 585 B
build/styles/block-library/post-comments-form/style-rtl.min.css 525 B
build/styles/block-library/post-comments-form/style.css 584 B
build/styles/block-library/post-comments-form/style.min.css 525 B
build/styles/block-library/post-comments-link/style-rtl.css 78 B
build/styles/block-library/post-comments-link/style-rtl.min.css 71 B
build/styles/block-library/post-comments-link/style.css 78 B
build/styles/block-library/post-comments-link/style.min.css 71 B
build/styles/block-library/post-content/style-rtl.css 68 B
build/styles/block-library/post-content/style-rtl.min.css 61 B
build/styles/block-library/post-content/style.css 68 B
build/styles/block-library/post-content/style.min.css 61 B
build/styles/block-library/post-date/style-rtl.css 69 B
build/styles/block-library/post-date/style-rtl.min.css 62 B
build/styles/block-library/post-date/style.css 69 B
build/styles/block-library/post-date/style.min.css 62 B
build/styles/block-library/post-excerpt/editor-rtl.css 78 B
build/styles/block-library/post-excerpt/editor-rtl.min.css 71 B
build/styles/block-library/post-excerpt/editor.css 78 B
build/styles/block-library/post-excerpt/editor.min.css 71 B
build/styles/block-library/post-excerpt/style-rtl.css 171 B
build/styles/block-library/post-excerpt/style-rtl.min.css 155 B
build/styles/block-library/post-excerpt/style.css 171 B
build/styles/block-library/post-excerpt/style.min.css 155 B
build/styles/block-library/post-featured-image/editor-rtl.css 1.14 kB
build/styles/block-library/post-featured-image/editor-rtl.min.css 719 B
build/styles/block-library/post-featured-image/editor.css 1.14 kB
build/styles/block-library/post-featured-image/editor.min.css 717 B
build/styles/block-library/post-featured-image/style-rtl.css 392 B
build/styles/block-library/post-featured-image/style-rtl.min.css 347 B
build/styles/block-library/post-featured-image/style.css 392 B
build/styles/block-library/post-featured-image/style.min.css 347 B
build/styles/block-library/post-navigation-link/style-rtl.css 234 B
build/styles/block-library/post-navigation-link/style-rtl.min.css 215 B
build/styles/block-library/post-navigation-link/style.css 245 B
build/styles/block-library/post-navigation-link/style.min.css 214 B
build/styles/block-library/post-template/style-rtl.css 1.27 kB
build/styles/block-library/post-template/style-rtl.min.css 441 B
build/styles/block-library/post-template/style.css 1.27 kB
build/styles/block-library/post-template/style.min.css 441 B
build/styles/block-library/post-terms/style-rtl.css 108 B
build/styles/block-library/post-terms/style-rtl.min.css 96 B
build/styles/block-library/post-terms/style.css 108 B
build/styles/block-library/post-terms/style.min.css 96 B
build/styles/block-library/post-time-to-read/style-rtl.css 77 B
build/styles/block-library/post-time-to-read/style-rtl.min.css 70 B
build/styles/block-library/post-time-to-read/style.css 77 B
build/styles/block-library/post-time-to-read/style.min.css 70 B
build/styles/block-library/post-title/style-rtl.css 175 B
build/styles/block-library/post-title/style-rtl.min.css 162 B
build/styles/block-library/post-title/style.css 175 B
build/styles/block-library/post-title/style.min.css 162 B
build/styles/block-library/preformatted/style-rtl.css 511 B
build/styles/block-library/preformatted/style-rtl.min.css 125 B
build/styles/block-library/preformatted/style.css 511 B
build/styles/block-library/preformatted/style.min.css 125 B
build/styles/block-library/pullquote/editor-rtl.css 146 B
build/styles/block-library/pullquote/editor-rtl.min.css 133 B
build/styles/block-library/pullquote/editor.css 146 B
build/styles/block-library/pullquote/editor.min.css 133 B
build/styles/block-library/pullquote/style-rtl.css 765 B
build/styles/block-library/pullquote/style-rtl.min.css 365 B
build/styles/block-library/pullquote/style.css 764 B
build/styles/block-library/pullquote/style.min.css 365 B
build/styles/block-library/pullquote/theme-rtl.css 195 B
build/styles/block-library/pullquote/theme-rtl.min.css 176 B
build/styles/block-library/pullquote/theme.css 195 B
build/styles/block-library/pullquote/theme.min.css 176 B
build/styles/block-library/query-pagination-numbers/editor-rtl.css 134 B
build/styles/block-library/query-pagination-numbers/editor-rtl.min.css 121 B
build/styles/block-library/query-pagination-numbers/editor.css 144 B
build/styles/block-library/query-pagination-numbers/editor.min.css 118 B
build/styles/block-library/query-pagination/editor-rtl.css 168 B
build/styles/block-library/query-pagination/editor-rtl.min.css 154 B
build/styles/block-library/query-pagination/editor.css 168 B
build/styles/block-library/query-pagination/editor.min.css 154 B
build/styles/block-library/query-pagination/style-rtl.css 254 B
build/styles/block-library/query-pagination/style-rtl.min.css 237 B
build/styles/block-library/query-pagination/style.css 265 B
build/styles/block-library/query-pagination/style.min.css 237 B
build/styles/block-library/query-title/style-rtl.css 71 B
build/styles/block-library/query-title/style-rtl.min.css 64 B
build/styles/block-library/query-title/style.css 71 B
build/styles/block-library/query-title/style.min.css 64 B
build/styles/block-library/query-total/style-rtl.css 71 B
build/styles/block-library/query-total/style-rtl.min.css 64 B
build/styles/block-library/query-total/style.css 71 B
build/styles/block-library/query-total/style.min.css 64 B
build/styles/block-library/query/editor-rtl.css 1.28 kB
build/styles/block-library/query/editor-rtl.min.css 438 B
build/styles/block-library/query/editor.css 1.28 kB
build/styles/block-library/query/editor.min.css 438 B
build/styles/block-library/quote/style-rtl.css 255 B
build/styles/block-library/quote/style-rtl.min.css 238 B
build/styles/block-library/quote/style.css 256 B
build/styles/block-library/quote/style.min.css 238 B
build/styles/block-library/quote/theme-rtl.css 253 B
build/styles/block-library/quote/theme-rtl.min.css 233 B
build/styles/block-library/quote/theme.css 254 B
build/styles/block-library/quote/theme.min.css 236 B
build/styles/block-library/read-more/style-rtl.css 146 B
build/styles/block-library/read-more/style-rtl.min.css 131 B
build/styles/block-library/read-more/style.css 146 B
build/styles/block-library/read-more/style.min.css 131 B
build/styles/block-library/reset-rtl.css 936 B
build/styles/block-library/reset-rtl.min.css 467 B
build/styles/block-library/reset.css 936 B
build/styles/block-library/reset.min.css 467 B
build/styles/block-library/rss/editor-rtl.css 144 B
build/styles/block-library/rss/editor-rtl.min.css 126 B
build/styles/block-library/rss/editor.css 144 B
build/styles/block-library/rss/editor.min.css 126 B
build/styles/block-library/rss/style-rtl.css 1.11 kB
build/styles/block-library/rss/style-rtl.min.css 284 B
build/styles/block-library/rss/style.css 1.12 kB
build/styles/block-library/rss/style.min.css 283 B
build/styles/block-library/search/editor-rtl.css 217 B
build/styles/block-library/search/editor-rtl.min.css 199 B
build/styles/block-library/search/editor.css 217 B
build/styles/block-library/search/editor.min.css 199 B
build/styles/block-library/search/style-rtl.css 1.1 kB
build/styles/block-library/search/style-rtl.min.css 665 B
build/styles/block-library/search/style.css 1.1 kB
build/styles/block-library/search/style.min.css 666 B
build/styles/block-library/search/theme-rtl.css 130 B
build/styles/block-library/search/theme-rtl.min.css 113 B
build/styles/block-library/search/theme.css 130 B
build/styles/block-library/search/theme.min.css 113 B
build/styles/block-library/separator/editor-rtl.css 106 B
build/styles/block-library/separator/editor-rtl.min.css 100 B
build/styles/block-library/separator/editor.css 106 B
build/styles/block-library/separator/editor.min.css 100 B
build/styles/block-library/separator/style-rtl.css 284 B
build/styles/block-library/separator/style-rtl.min.css 248 B
build/styles/block-library/separator/style.css 297 B
build/styles/block-library/separator/style.min.css 248 B
build/styles/block-library/separator/theme-rtl.css 226 B
build/styles/block-library/separator/theme-rtl.min.css 195 B
build/styles/block-library/separator/theme.css 226 B
build/styles/block-library/separator/theme.min.css 195 B
build/styles/block-library/shortcode/editor-rtl.css 1.1 kB
build/styles/block-library/shortcode/editor-rtl.min.css 286 B
build/styles/block-library/shortcode/editor.css 1.1 kB
build/styles/block-library/shortcode/editor.min.css 286 B
build/styles/block-library/site-logo/editor-rtl.css 1.12 kB
build/styles/block-library/site-logo/editor-rtl.min.css 696 B
build/styles/block-library/site-logo/editor.css 1.12 kB
build/styles/block-library/site-logo/editor.min.css 692 B
build/styles/block-library/site-logo/style-rtl.css 239 B
build/styles/block-library/site-logo/style-rtl.min.css 218 B
build/styles/block-library/site-logo/style.css 238 B
build/styles/block-library/site-logo/style.min.css 218 B
build/styles/block-library/site-tagline/editor-rtl.css 94 B
build/styles/block-library/site-tagline/editor-rtl.min.css 87 B
build/styles/block-library/site-tagline/editor.css 94 B
build/styles/block-library/site-tagline/editor.min.css 87 B
build/styles/block-library/site-tagline/style-rtl.css 72 B
build/styles/block-library/site-tagline/style-rtl.min.css 65 B
build/styles/block-library/site-tagline/style.css 72 B
build/styles/block-library/site-tagline/style.min.css 65 B
build/styles/block-library/site-title/editor-rtl.css 93 B
build/styles/block-library/site-title/editor-rtl.min.css 85 B
build/styles/block-library/site-title/editor.css 93 B
build/styles/block-library/site-title/editor.min.css 85 B
build/styles/block-library/site-title/style-rtl.css 153 B
build/styles/block-library/site-title/style-rtl.min.css 143 B
build/styles/block-library/site-title/style.css 153 B
build/styles/block-library/site-title/style.min.css 143 B
build/styles/block-library/social-link/editor-rtl.css 346 B
build/styles/block-library/social-link/editor-rtl.min.css 314 B
build/styles/block-library/social-link/editor.css 348 B
build/styles/block-library/social-link/editor.min.css 314 B
build/styles/block-library/social-links/editor-rtl.css 737 B
build/styles/block-library/social-links/editor-rtl.min.css 339 B
build/styles/block-library/social-links/editor.css 738 B
build/styles/block-library/social-links/editor.min.css 338 B
build/styles/block-library/social-links/style-rtl.css 1.57 kB
build/styles/block-library/social-links/style-rtl.min.css 1.51 kB
build/styles/block-library/social-links/style.css 1.57 kB
build/styles/block-library/social-links/style.min.css 1.51 kB
build/styles/block-library/spacer/editor-rtl.css 774 B
build/styles/block-library/spacer/editor-rtl.min.css 346 B
build/styles/block-library/spacer/editor.css 774 B
build/styles/block-library/spacer/editor.min.css 346 B
build/styles/block-library/spacer/style-rtl.css 55 B
build/styles/block-library/spacer/style-rtl.min.css 48 B
build/styles/block-library/spacer/style.css 55 B
build/styles/block-library/spacer/style.min.css 48 B
build/styles/block-library/style-rtl.css 21.5 kB
build/styles/block-library/style-rtl.min.css 18 kB
build/styles/block-library/style.css 21.6 kB
build/styles/block-library/style.min.css 18 kB
build/styles/block-library/tab-list/editor-rtl.css 107 B
build/styles/block-library/tab-list/editor-rtl.min.css 97 B
build/styles/block-library/tab-list/editor.css 107 B
build/styles/block-library/tab-list/editor.min.css 97 B
build/styles/block-library/tab-panel/style-rtl.css 238 B
build/styles/block-library/tab-panel/style-rtl.min.css 215 B
build/styles/block-library/tab-panel/style.css 238 B
build/styles/block-library/tab-panel/style.min.css 215 B
build/styles/block-library/tab-panels/style-rtl.css 76 B
build/styles/block-library/tab-panels/style-rtl.min.css 65 B
build/styles/block-library/tab-panels/style.css 76 B
build/styles/block-library/tab-panels/style.min.css 65 B
build/styles/block-library/tab/editor-rtl.css 160 B
build/styles/block-library/tab/editor-rtl.min.css 148 B
build/styles/block-library/tab/editor.css 160 B
build/styles/block-library/tab/editor.min.css 148 B
build/styles/block-library/tab/style-rtl.css 397 B
build/styles/block-library/tab/style-rtl.min.css 352 B
build/styles/block-library/tab/style.css 398 B
build/styles/block-library/tab/style.min.css 356 B
build/styles/block-library/table-of-contents/style-rtl.css 89 B
build/styles/block-library/table-of-contents/style-rtl.min.css 83 B
build/styles/block-library/table-of-contents/style.css 89 B
build/styles/block-library/table-of-contents/style.min.css 83 B
build/styles/block-library/table/editor-rtl.css 1.25 kB
build/styles/block-library/table/editor-rtl.min.css 394 B
build/styles/block-library/table/editor.css 1.25 kB
build/styles/block-library/table/editor.min.css 394 B
build/styles/block-library/table/style-rtl.css 1.06 kB
build/styles/block-library/table/style-rtl.min.css 641 B
build/styles/block-library/table/style.css 1.06 kB
build/styles/block-library/table/style.min.css 640 B
build/styles/block-library/table/theme-rtl.css 985 B
build/styles/block-library/table/theme-rtl.min.css 152 B
build/styles/block-library/table/theme.css 985 B
build/styles/block-library/table/theme.min.css 152 B
build/styles/block-library/tabs/style-rtl.css 64 B
build/styles/block-library/tabs/style-rtl.min.css 57 B
build/styles/block-library/tabs/style.css 64 B
build/styles/block-library/tabs/style.min.css 57 B
build/styles/block-library/tag-cloud/style-rtl.css 283 B
build/styles/block-library/tag-cloud/style-rtl.min.css 248 B
build/styles/block-library/tag-cloud/style.css 283 B
build/styles/block-library/tag-cloud/style.min.css 248 B
build/styles/block-library/template-part/editor-rtl.css 1.2 kB
build/styles/block-library/template-part/editor-rtl.min.css 368 B
build/styles/block-library/template-part/editor.css 1.2 kB
build/styles/block-library/template-part/editor.min.css 368 B
build/styles/block-library/template-part/theme-rtl.css 492 B
build/styles/block-library/template-part/theme-rtl.min.css 113 B
build/styles/block-library/template-part/theme.css 492 B
build/styles/block-library/template-part/theme.min.css 113 B
build/styles/block-library/term-count/style-rtl.css 70 B
build/styles/block-library/term-count/style-rtl.min.css 63 B
build/styles/block-library/term-count/style.css 70 B
build/styles/block-library/term-count/style.min.css 63 B
build/styles/block-library/term-description/style-rtl.css 138 B
build/styles/block-library/term-description/style-rtl.min.css 126 B
build/styles/block-library/term-description/style.css 138 B
build/styles/block-library/term-description/style.min.css 126 B
build/styles/block-library/term-name/style-rtl.css 69 B
build/styles/block-library/term-name/style-rtl.min.css 62 B
build/styles/block-library/term-name/style.css 69 B
build/styles/block-library/term-name/style.min.css 62 B
build/styles/block-library/term-template/editor-rtl.css 267 B
build/styles/block-library/term-template/editor-rtl.min.css 225 B
build/styles/block-library/term-template/editor.css 267 B
build/styles/block-library/term-template/editor.min.css 225 B
build/styles/block-library/term-template/style-rtl.css 124 B
build/styles/block-library/term-template/style-rtl.min.css 114 B
build/styles/block-library/term-template/style.css 124 B
build/styles/block-library/term-template/style.min.css 114 B
build/styles/block-library/text-columns/editor-rtl.css 481 B
build/styles/block-library/text-columns/editor-rtl.min.css 95 B
build/styles/block-library/text-columns/editor.css 481 B
build/styles/block-library/text-columns/editor.min.css 95 B
build/styles/block-library/text-columns/style-rtl.css 177 B
build/styles/block-library/text-columns/style-rtl.min.css 165 B
build/styles/block-library/text-columns/style.css 177 B
build/styles/block-library/text-columns/style.min.css 165 B
build/styles/block-library/theme-rtl.css 1.59 kB
build/styles/block-library/theme-rtl.min.css 715 B
build/styles/block-library/theme.css 1.6 kB
build/styles/block-library/theme.min.css 719 B
build/styles/block-library/verse/style-rtl.css 155 B
build/styles/block-library/verse/style-rtl.min.css 137 B
build/styles/block-library/verse/style.css 155 B
build/styles/block-library/verse/style.min.css 137 B
build/styles/block-library/video/editor-rtl.css 839 B
build/styles/block-library/video/editor-rtl.min.css 428 B
build/styles/block-library/video/editor.css 840 B
build/styles/block-library/video/editor.min.css 428 B
build/styles/block-library/video/style-rtl.css 1.02 kB
build/styles/block-library/video/style-rtl.min.css 202 B
build/styles/block-library/video/style.css 1.02 kB
build/styles/block-library/video/style.min.css 202 B
build/styles/block-library/video/theme-rtl.css 967 B
build/styles/block-library/video/theme-rtl.min.css 134 B
build/styles/block-library/video/theme.css 967 B
build/styles/block-library/video/theme.min.css 134 B
build/styles/commands/style-rtl.css 2.07 kB
build/styles/commands/style-rtl.min.css 1.17 kB
build/styles/commands/style.css 2.06 kB
build/styles/commands/style.min.css 1.17 kB
build/styles/components/style-rtl.css 17.3 kB
build/styles/components/style-rtl.min.css 14.1 kB
build/styles/components/style.css 17.4 kB
build/styles/components/style.min.css 14.1 kB
build/styles/customize-widgets/style-rtl.css 2.35 kB
build/styles/customize-widgets/style-rtl.min.css 1.44 kB
build/styles/customize-widgets/style.css 2.35 kB
build/styles/customize-widgets/style.min.css 1.44 kB
build/styles/edit-post/classic-rtl.css 1.29 kB
build/styles/edit-post/classic-rtl.min.css 425 B
build/styles/edit-post/classic.css 1.31 kB
build/styles/edit-post/classic.min.css 428 B
build/styles/edit-post/style-rtl.css 3.51 kB
build/styles/edit-post/style-rtl.min.css 2.21 kB
build/styles/edit-post/style.css 3.51 kB
build/styles/edit-post/style.min.css 2.21 kB
build/styles/edit-site/style-rtl.css 20.3 kB
build/styles/edit-site/style-rtl.min.css 16.5 kB
build/styles/edit-site/style.css 20.4 kB
build/styles/edit-site/style.min.css 16.5 kB
build/styles/edit-widgets/style-rtl.css 4.85 kB
build/styles/edit-widgets/style-rtl.min.css 3.52 kB
build/styles/edit-widgets/style.css 4.85 kB
build/styles/edit-widgets/style.min.css 3.52 kB
build/styles/editor/style-rtl.css 27.1 kB
build/styles/editor/style-rtl.min.css 22.8 kB
build/styles/editor/style.css 27.1 kB
build/styles/editor/style.min.css 22.8 kB
build/styles/format-library/style-rtl.css 735 B
build/styles/format-library/style-rtl.min.css 326 B
build/styles/format-library/style.css 746 B
build/styles/format-library/style.min.css 326 B
build/styles/list-reusable-blocks/style-rtl.css 1.07 kB
build/styles/list-reusable-blocks/style-rtl.min.css 250 B
build/styles/list-reusable-blocks/style.css 1.07 kB
build/styles/list-reusable-blocks/style.min.css 249 B
build/styles/media-utils/style-rtl.css 2.08 kB
build/styles/media-utils/style-rtl.min.css 1.17 kB
build/styles/media-utils/style.css 2.08 kB
build/styles/media-utils/style.min.css 1.17 kB
build/styles/nux/style-rtl.css 1.48 kB
build/styles/nux/style-rtl.min.css 622 B
build/styles/nux/style.css 1.5 kB
build/styles/nux/style.min.css 618 B
build/styles/patterns/style-rtl.css 1.46 kB
build/styles/patterns/style-rtl.min.css 611 B
build/styles/patterns/style.css 1.46 kB
build/styles/patterns/style.min.css 611 B
build/styles/preferences/style-rtl.css 1.26 kB
build/styles/preferences/style-rtl.min.css 415 B
build/styles/preferences/style.css 1.26 kB
build/styles/preferences/style.min.css 415 B
build/styles/reusable-blocks/style-rtl.css 1.11 kB
build/styles/reusable-blocks/style-rtl.min.css 275 B
build/styles/reusable-blocks/style.css 1.11 kB
build/styles/reusable-blocks/style.min.css 275 B
build/styles/widgets/style-rtl.css 2.05 kB
build/styles/widgets/style-rtl.min.css 1.16 kB
build/styles/widgets/style.css 2.06 kB
build/styles/widgets/style.min.css 1.16 kB

compressed-size-action

@ciampo ciampo changed the title Dialog: Make Dialog.Header and Dialog.Footer sticky ui/Dialog and ui/AlertDialog: support sticky header and footer Apr 22, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 22, 2026

Flaky tests detected in 4633f0a.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/25016178673
📝 Reported issues:

@ciampo ciampo requested a review from Copilot April 22, 2026 14:05
@ciampo ciampo added the [Type] Enhancement A suggestion for improvement. label Apr 22, 2026
@ciampo ciampo self-assigned this Apr 22, 2026

This comment was marked as resolved.

@ciampo ciampo marked this pull request as ready for review April 22, 2026 15:44
@ciampo ciampo requested a review from a team as a code owner April 22, 2026 15:44
@ciampo ciampo requested a review from jameskoster April 22, 2026 15:44
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 22, 2026

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: ciampo <mciampini@git.wordpress.org>
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: jameskoster <jameskoster@git.wordpress.org>
Co-authored-by: tyxla <tyxla@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@jameskoster
Copy link
Copy Markdown
Contributor

Looks good. Did you consider making only the content area scrollable rather than the entire dialog? I ask only because the appearance might be a bit neater if the scrollbar is constrained to the middle portion of the layout. If there's a technical reason to handle it this way that's fine.

Comment thread packages/ui/src/alert-dialog/stories/index.story.tsx Outdated
Comment thread packages/ui/src/alert-dialog/types.ts Outdated
Comment thread packages/ui/src/dialog/style.module.css Outdated
Comment thread packages/ui/src/drawer/style.module.css Outdated
Comment thread packages/ui/src/dialog/style.module.css
Comment thread packages/ui/src/dialog/style.module.css Outdated
Comment thread packages/ui/src/dialog/style.module.css Outdated
@mirka
Copy link
Copy Markdown
Member

mirka commented Apr 23, 2026

Just noticed that the PR title and description don't mention changes to Drawer.

@tyxla
Copy link
Copy Markdown
Member

tyxla commented Apr 23, 2026

Just noticed that the PR title and description don't mention changes to Drawer.

See also #77605 where @ntsekouras has been utilizing Drawer with a fixed header/footer. Would love to avoid all those overrides and support it all out of the box!

@ciampo
Copy link
Copy Markdown
Contributor Author

ciampo commented Apr 24, 2026

@tyxla sounds good, I'll bump the priority of this PR in my queue

@ciampo ciampo force-pushed the add/dialog-sticky-chrome branch from 854138c to a4b30d8 Compare April 24, 2026 14:43
@ciampo ciampo changed the title ui/Dialog and ui/AlertDialog: support sticky header and footer ui/Dialog, ui/AlertDialog, ui/Drawer: support sticky header and footer Apr 24, 2026
@ciampo
Copy link
Copy Markdown
Contributor Author

ciampo commented Apr 24, 2026

@jameskoster

Did you consider making only the content area scrollable rather than the entire dialog? I ask only because the appearance might be a bit neater if the scrollbar is constrained to the middle portion of the layout. If there's a technical reason to handle it this way that's fine.

It can be done, but the cleanest way that I can think of will introduce new (mandatory) Dialog.Content and Drawer.Content component.

I will try to implement it and we can decide whether we prefer it to the current implementation.

@ciampo
Copy link
Copy Markdown
Contributor Author

ciampo commented Apr 24, 2026

@jameskoster done — pushed in f6579af. The scroll region now lives on a dedicated element (Dialog.Content / Drawer.Content, or an internal container for AlertDialog), so the scrollbar is confined to the body between the pinned chrome.

API-wise:

  • Dialog and Drawer gain a Content subcomponent that's required for scrolling. Render Header / Footer as siblings of Content to pin them, or nest them inside Content to scroll with the body — the sticky opt-out is now purely a DOM-placement thing, so the sticky prop is gone.
  • AlertDialog keeps stickyHeader / stickyFooter props since its header/footer are internal and not composable.

I updated stories and docs accordingly, together with a few more fixes/improvements.

ciampo added 22 commits April 27, 2026 21:29
Extracts a new `overlay-chrome.module.css` that centralizes the sticky
chrome primitives — `.header-chrome`, `.footer-chrome`, `.header-sticky`,
`.footer-sticky`, `.header`/`.footer` flex layouts, `.title` — and all
scroll-container behavior (yield-and-reclaim padding, data-attribute
driven separator coloring, focus-aware scroll-padding). All rules key
off `[data-wp-ui-overlay-scroll-container]`, which `useOverlayScrollStateAttributes`
now toggles on attach, so the shared CSS applies uniformly regardless of
which element actually scrolls.

Dialog and AlertDialog consume the shared primitives directly and lose
their duplicated rules. AlertDialog's heading also gains the shared
`.title` class so its color and GCD defenses match Dialog/Drawer.

For Drawer, scroll ownership moves from `_Drawer.Popup` to
`_Drawer.Content` (which already carries the inner padding and safe-area
insets). Base UI's swipe-dismiss-on-scroll-edge logic discovers the
scrollable element dynamically, so the move is transparent to it. The
content element gets `block-size: 100%` plus a viewport-minus-inset
`max-block-size` cap on up/down drawers so `is-auto` sizing still yields
a scrollable box. Drawer's default spacing now matches Dialog/AlertDialog
(symmetric `gap-lg` around chrome) — the explicit `padding-top` and
`margin-block` overrides on `.header`/`.footer` are no longer needed.

Class names migrated to kebab-case (`.header-sticky`, `.footer-sticky`)
as part of the same pass.

Made-with: Cursor
Extends the `Scrollable` stories with inline size knobs (and a swipe
direction knob on Drawer) so reviewers can exercise sticky chrome
against every popup variant without re-mounting the story. AlertDialog
and Popover are intentionally skipped: neither exposes a `size` prop.

Drawer's story switches from `args.children` to a `render` function so
state can drive both `Drawer.Popup size` and `Drawer.Root swipeDirection`.

Made-with: Cursor
- Scrollable story now passes `<ScrollableContent />` via `args.children`
  instead of hardcoding it inside a custom `render`, matching the other
  AlertDialog stories.
- Trim the `stickyHeader` / `stickyFooter` JSDocs to drop the separator
  border detail — that's a stylistic implementation detail, not part of
  the API contract.

Made-with: Cursor
Move `overscroll-behavior: contain` out of the per-component CSS and into
`overlay-chrome.module.css`, guarded by a new
`data-wp-ui-overlay-modal` attribute. Dialog and Drawer mirror their
`modal` prop onto the popup; AlertDialog sets it unconditionally since
it's always modal. Non-modal Dialog / Drawer no longer trap scroll,
which matches the intuition that a non-modal overlay lets the page
underneath remain scrollable.

The shared rule uses two selectors to cover both scroll-container
topologies in the codebase: one for Dialog/AlertDialog where the popup
is itself the scroll container, and one for Drawer where the scroll
container sits inside the popup (`_Drawer.Content`).

Made-with: Cursor
Introduces `Dialog.Content` and `Drawer.Content` as the scroll-owning
region of each overlay, confining the scrollbar to the body area between
the pinned header and footer (Jay's request on #77559). The pinned-vs-
scrolls-with-body toggle for `Header` / `Footer` moves from a `sticky`
prop to pure DOM placement: render the chrome as a sibling of `Content`
to pin it, nest it inside `Content` to scroll with the body.

`AlertDialog` keeps `stickyHeader` / `stickyFooter` props because its
chrome is internal and not composable — the props now control sibling-
vs-nested placement around an internal scroll container.

Shared `overlay-chrome.module.css` centralizes the chrome + scroll-
container layout. The separator line lives on the pinned chrome (not
on `Content`) and is reserved via a `1px solid transparent` border on
the inner block side only when pinned; scroll state toggles the color.
The pinned chrome subtracts 1px from its adjacent padding so the
border-box height is identical whether pinned or not. Nested chrome
inside `Content` collapses its block-outer and inline padding to avoid
stacking on top of `Content`'s padding.

Storybook `Scrollable` stories for Dialog and Drawer expose a "Sticky
header / Sticky footer" toggle that flips the DOM placement at runtime.

PR description, CHANGELOG, and JSDoc updated to reflect the new API.

Made-with: Cursor
Switches the shared overlay-chrome sibling rules from adjacent (`+`)
to general-sibling (`~`) combinators, so extra elements between
`.header` / `.content` / `.footer` (custom wrappers, an a11y live
region, etc.) don't silently break the pinned layout. Expected DOM
shape is still `header → content → footer`; intermediate siblings are
now tolerated.

Also splits the nested-chrome padding rules so `padding-inline: 0`
applies to any `.content > .header` / `.footer` regardless of its
position inside `.content`, while `padding-block-*: 0` stays guarded
by `:first-child` / `:last-child`. A nested header that isn't the
first child of `.content` (e.g. preceded by a `VisuallyHidden` shim)
still collapses inline padding correctly; block padding only
collapses at the scroll-edge position where it's semantically right.

Adds a forced-colors note explaining why the transparent separator
border still works in FC mode without extra rules: `transparent`
border-color is preserved per spec, and the toggled-in token color is
substituted with `CanvasText` by the UA.

Made-with: Cursor
Dropping `overflow: auto` from `Dialog.Popup` / `Drawer.Popup` means
existing consumers that don't wrap body content in `Dialog.Content` /
`Drawer.Content` now silently clip instead of scrolling. That is a
behavior change and belongs under Breaking Changes, not Enhancements.
Keeps a short Enhancements line for the actual feature (pinned chrome
+ separator colorization) and points to the Breaking Changes entry
for the API details.

Made-with: Cursor
WCAG 2.1.1 requires scrollable regions to be reachable by keyboard.
`useOverlayScrollStateAttributes` now toggles `tabindex="0"` on its
target only while the element actually overflows; a non-scrolling
`<div tabindex="0">` is a tab-stop anti-pattern, so we remove it as
soon as the overflow disappears. A `data-wp-ui-overlay-scroll-tabbable`
flag marks hook-installed tabindex so a consumer-supplied `tabindex`
is never overwritten.

To keep the newly tabbable scroll container from stealing initial
focus when a real interactive control is available, widen
`useDeprioritizedInitialFocus` to accept a list of deprioritized
attributes, and include `data-wp-ui-overlay-scroll-container` in that
list from `Dialog.Popup` / `Drawer.Popup`. Popover keeps its single
close-icon attribute since it has no managed scroll container.

The `SCROLL_CONTAINER_ATTR` constant is exported from
`use-overlay-scroll-state-attributes` so the popups can reference it
without stringly-coupling to the library-internal data attribute.

Made-with: Cursor
Covers the JSDOM-reachable behavior of the new scroll-container
architecture: ref forwarding on the new `Content` subcomponent,
sibling-vs-nested chrome placement, `data-wp-ui-overlay-modal`
mirroring (including the non-modal and `trap-focus` cases where the
attribute must be absent), composed `onScroll`,
`data-wp-ui-overlay-scroll-container` target, and the overflow-driven
`tabindex` toggle (exercised by stubbing `scrollHeight` /
`clientHeight` / `scrollTop` on the element). AlertDialog also
verifies that `stickyHeader` / `stickyFooter` reparent the internal
chrome into or out of the scroller.

Purely style-dependent behavior — separator visibility, sticky
positioning, `overscroll-behavior`, the ResizeObserver-driven update
loop — isn't testable in JSDOM and stays out of scope.

Made-with: Cursor
The previous `childList`-only MutationObserver at the scroll container
missed descendant mutations that don't propagate a resize up to a
direct child — e.g. rows appended to a nested list, a lazy component
mounting into a fixed-size wrapper. With the scroll container at
\`flex: 1 1 auto; min-block-size: 0\`, its own box doesn't resize as
content grows beyond the viewport, so the ResizeObserver alone
wouldn't refresh attributes either, and the newly overflowing region
could stay without a \`tabindex\` until the user happened to scroll.

Widen the MutationObserver to \`{ childList: true, subtree: true }\` so
any descendant addition / removal triggers an attribute refresh. The
callback still only re-observes ResizeObserver targets for direct
children — subtree observation is purely for discovery, keeping the
per-mutation cost O(1) regardless of tree depth.

Made-with: Cursor
…lling

Overlay popups don't support horizontal scroll: the chrome layout is
block-axis only, and `useOverlayScrollStateAttributes` tracks overflow
on the block axis alone. Make that contract explicit in CSS by adding
`overflow-inline: hidden` to the shared `.content` scroll container,
and call it out in the hook's JSDoc so consumers know to constrain
content width rather than expect a horizontal scrollbar.

No visual change in practice — the popup's own `overflow: hidden`
already clipped wide content; this just moves the clip to the scroll
container where the intent is explicit and the behavior doesn't
accidentally rely on the popup's clipping.

Made-with: Cursor
Bundled small follow-ups from self-review:

- \`useOverlayScrollStateAttributes\`: document the
  consumer-pre-set-tabindex edge case inline so the trade-off around
  the \`data-wp-ui-overlay-scroll-tabbable\` flag is obvious, and
  comment the cleanup-path guard that uses it.
- \`useDeprioritizedInitialFocus\`: drop the \`readonly\` on
  \`deprioritizedAttributes\` (all call sites pass plain arrays) and
  update the inline comment to call out that Base UI wraps
  \`initialFocus\` in \`useValueAsRef\`, so reference identity doesn't
  matter and skipping \`useMemo\` is intentional.
- \`AlertDialog\` test \`findScroller\`: distinguish "popup ref not
  attached" from "scroller not inside popup" for faster debugging
  when a test setup is wrong.
- \`CHANGELOG\`: make the sticky-chrome entries concise — one line
  for the breaking change, one line for the enhancement.

Made-with: Cursor
- Add a scroll-state attribute test per component
  (Dialog / Drawer / AlertDialog) that stubs \`scrollHeight\` /
  \`clientHeight\` / \`scrollTop\` and dispatches scroll events,
  asserting \`data-wp-ui-overlay-scrolled-from-top\` and
  \`data-wp-ui-overlay-scrolled-from-bottom\` toggle correctly as
  the simulated scroll position moves from top → middle → bottom.
- Tighten the pinned-chrome tests for Dialog and Drawer by also
  asserting the header comes *before* the scroll container in
  document order via \`compareDocumentPosition\` — the CSS
  sibling-based separator rules depend on that.

No JSDOM workarounds we can avoid here: the harness doesn't compute
layout, so initial-mount overflow detection can't be covered without
stubbing. Attribute-toggle + DOM-order coverage is the best fidelity
available without switching to a real-browser runner.

Made-with: Cursor
\`Dialog.Content\` and \`Drawer.Content\` used to inherit \`tabIndex\`
implicitly from \`ComponentProps<'div'>\`, which meant the
auto-tabbable-when-overflowing behavior and the consumer-override
edge case were only documented in the hook itself. Re-declare
\`tabIndex\` explicitly on both \`ContentProps\` types with JSDoc
that spells out:

- the automatic \`tabindex="0"\` that the component installs on
  overflow (WCAG 2.1.1);
- that an explicit \`tabIndex\` is never overwritten, including
  \`tabIndex={ -1 }\` as a deliberate opt-out;
- the single edge case: removing an explicit \`tabIndex\` at
  runtime lets the component take management back over, because
  a prior explicit value can't be distinguished from "unset".

No runtime change — this is purely documentation surfaced in
IDE tooltips and generated docs.

Made-with: Cursor
When the body overflows, the scroll container becomes a keyboard tab
stop so users can arrow-scroll it — but without a focus ring, there
was no visible indication that focus had landed there. Compose the
shared \`outset-ring--focus-visible\` utility onto \`Dialog.Content\`,
\`Drawer.Content\`, and the AlertDialog internal scroll container so
the ring matches the color, width, and transition used by every
other focusable surface in the design system.

Made-with: Cursor
The shared \`outset-ring--focus-visible\` utility offsets the ring
\`+1px\` outside the element. For the overlay scroll container, that
puts the ring at the popup's rounded corners, where \`overflow:
hidden\` on the popup clips it. Override \`outline-offset\` to a
negative value (keyed off \`--wpds-border-width-focus\` so it tracks
the token) in the shared \`.content\` rule so the ring sits flush
inside the content box and stays fully visible within the popup's
clipping region. All other visual properties (color, width,
transition) still come from the shared utility.

Made-with: Cursor
When `AlertDialog` body content overflows, the internal scroll
container becomes `tabindex="0"` so keyboard users can arrow-scroll it
(WCAG 2.1.1). Without `useDeprioritizedInitialFocus` wired up, that
container would be the first tabbable element in the popup at open
time, stealing initial focus from the Cancel/OK actions.

Mirror the wiring already present on Dialog and Drawer: route
`_AlertDialog.Popup` through `useDeprioritizedInitialFocus` with
`[SCROLL_CONTAINER_ATTR]` (no close icon to skip on AlertDialog) and
forward the resolved callback as `initialFocus`. Add a regression
test that forces the scroller into the overflowing state via
`Element.prototype` getter stubs and asserts focus settles on the
Cancel button.

Made-with: Cursor
Round 4's switch to `subtree: true` was meant to catch async content
added deep inside the scroll region (rows in a nested list, a lazy
component mounting into a wrapper that doesn't itself resize, …).
In practice, anything whose growth actually changes the scroll size
already propagates a resize up the layout tree and is caught by the
existing `ResizeObserver` on direct children — and `subtree: true`
fans the callback out over every text-node insertion in
content-heavy overlays (rich-text editors, virtualized lists), which
isn't worth the cost of the rare deep-mutation-without-resize case.

Revert to `{ childList: true }` only and document the trade-off in
the hook's JSDoc, calling out rAF-coalescing as the future
mitigation if a real consumer ever needs the broader observation.

Made-with: Cursor
Two small cleanups that don't change behavior:

- Loop over a `HOOK_OWNED_ATTRS` constant in `cleanupScrollAttributes`
  instead of three open-coded `removeAttribute` calls, so future
  attributes added to the hook don't have to remember to extend the
  cleanup path.
- Annotate `SCROLL_TABBABLE_FLAG_ATTR` with a note that its literal
  string is named in the public JSDoc as a debugging breadcrumb, so
  anyone tempted to rename it remembers to update the JSDoc too.

Made-with: Cursor
Rephrase the existing comment block on the overlay scroll
container's `:focus-visible` rule so it reads as cause →
consequence: the shared utility's `+1px` outline-offset would
collide with the popup's rounded clipping region, so we override to
a negative offset that sits inside the content box.

Also call out that the shared utility's `transition: outline …`
shorthand only animates outline-color and outline-width, not
outline-offset — so the negative offset applied here is static by
design, with the visible fade-in/out coming from color change
only. Future maintainers seeing "non-transitioned offset on top of
a transitioned outline" won't need to re-derive the trade-off.

Made-with: Cursor
Extend the JSDoc on `Dialog.ContentProps` and `Drawer.ContentProps`
`tabIndex` to call out that the scroll region intentionally renders
without `role` / `aria-label`. The auto-managed `tabindex="0"` makes
the region keyboard-reachable when it overflows (WCAG 2.1.1), but a
generic landmark role announcement would land on top of the dialog's
own heading + body context and be redundant for screen-reader users.

Surfaces the trade-off in the same place a consumer would see when
deciding whether to override `tabIndex` themselves.

Made-with: Cursor
If the hook installed `tabindex="0"` on an overflowing scroll container
and the consumer later passed an explicit `tabIndex` prop, the internal
"this is ours" flag stayed in place. A subsequent non-overflow cleanup
tick would then strip the consumer's value, contradicting the contract
documented on `Dialog.Content` / `Drawer.Content`.

Reconcile at the start of every update and cleanup: when the flag is
set but the current `tabindex` is no longer `"0"`, drop just the flag
and leave the consumer's value alone. Add a regression test that
flips overflow on, mutates the attribute to simulate takeover, then
flips overflow off and asserts the consumer's value survives.

Made-with: Cursor
@ciampo ciampo force-pushed the add/dialog-sticky-chrome branch from 9f10dc6 to e03e9a4 Compare April 27, 2026 19:43
ciampo added 3 commits April 27, 2026 21:51
Tighten the JSDoc on `useOverlayScrollStateAttributes` so the tabindex
contract reads as a single labelled list rather than scattered prose:

- Pre-install opt-out (consumer sets `tabindex` before first overflow).
- Takeover after install (consumer overrides the hook's `"0"` later).
- Indistinguishable case (`tabIndex={ 0 }` collides with the managed
  value, so the hook can't tell them apart and may strip it on the
  next non-overflow tick).

Also reword the inline comments in `updateScrollAttributes` so each
comment block sits next to the code it describes (the takeover note
moves up next to the `reconcileTabbableFlag` call; the pre-install
opt-out note stays with the install branch). And expand the
`reconcileTabbableFlag` JSDoc to cross-reference the contract.

Comment-only change.

Made-with: Cursor
Document why the takeover regression test exercises only
`updateScrollAttributes` and not `cleanupScrollAttributes`: both
paths share `reconcileTabbableFlag`, so a single test guards both.
Flag the maintenance condition (split / inline the helper → add an
explicit unmount-after-takeover test) so future readers don't have
to re-derive the coverage rationale.

Comment-only change.

Made-with: Cursor
Reflect the takeover-after-install support and the
`tabIndex={ 0 }` indistinguishable case in the public JSDoc on
`Dialog.Content` and `Drawer.Content`. The wins-and-is-never-
overwritten guarantee now explicitly covers overrides applied after
the component had already managed the value, and a short bullet on
the `0`-collision corner case nudges consumers toward picking a
different value when they need the tabindex to stick across
overflow flips.

Doc-only change.

Made-with: Cursor
@ciampo ciampo merged commit 7d8facb into trunk Apr 27, 2026
39 of 40 checks passed
@ciampo ciampo deleted the add/dialog-sticky-chrome branch April 27, 2026 21:17
@github-actions github-actions Bot added this to the Gutenberg 23.1 milestone Apr 27, 2026
t-hamano pushed a commit that referenced this pull request Apr 29, 2026
#77559)

* Dialog: Make Dialog.Header and Dialog.Footer sticky

Keep the title and primary actions visible while long dialog content scrolls.
Separator borders fade in when content is hidden above or below the scroll
viewport. Opt out per sub-component with sticky={ false }.

Scroll state is exposed to CSS via data-wp-ui-dialog-scrolled-from-top /
-from-bottom attributes toggled imperatively in the popup's onScroll handler,
so the border color flips inside the same scroll frame with no React
re-render. Layout is driven by CSS only: the popup yields its block padding
to a flush-sticky header/footer via :has(), and the chrome extends across
the popup's horizontal padding so the separator border runs edge-to-edge.
will-change: transform pre-promotes the sticky chrome to its own compositor
layer to avoid a subpixel snap on the first stick. overscroll-behavior:
contain keeps scroll momentum inside the popup.

Closes #77180

Made-with: Cursor

* Dialog: Update CHANGELOG entry with PR number

Made-with: Cursor

* Dialog: Add sticky toggles to the Scrollable story

Add independent "Sticky header" and "Sticky footer" checkboxes to the
Scrollable story, placed both outside the trigger and inside the dialog
body (state is shared, matching the AllSizes pattern) so reviewers can
flip either prop without reopening the dialog.

Made-with: Cursor

* ui: Align AlertDialog sticky chrome with Dialog, add overlay scroll hook

- Add useOverlayScrollStateAttributes in utils; use data-wp-ui-overlay-scrolled-*
  attributes for reuse (e.g. future Drawer).
- Dialog: extract scroll wiring to the hook; split header/footer chrome classes
  for shared styling with AlertDialog.
- AlertDialog: sticky title/actions stacks via stickyHeader/stickyFooter on
  Popup; footer-actions row; Scrollable story with toggles.
- Update CHANGELOG Unreleased entry.

Made-with: Cursor

* ui: Compose Popup onScroll with overlay scroll-state handler

- useOverlayScrollStateAttributes: optional consumer onScroll runs after
  updating data-wp-ui-overlay-scrolled-* attributes.
- Dialog.Popup and AlertDialog.Popup: destructure onScroll from props and pass
  into the hook so callers are not overridden.
- Guard ResizeObserver behind typeof check (aligns with modal and avoids
  runtime throw when unavailable).

Made-with: Cursor

* ui/AlertDialog: preserve non-sticky vertical spacing

Restore trunk-equivalent spacing when sticky header/footer are toggled off.
Use shared dialog chrome only for sticky mode so sticky behavior remains while
non-sticky mode keeps the original AlertDialog vertical rhythm.

Made-with: Cursor

* ui/Dialog: Use inset box-shadow for sticky chrome separator

Switch the sticky header/footer chrome separator from a 1px transparent
border to an inset box-shadow so it never contributes to layout size.
This keeps vertical rhythm identical to trunk's non-sticky spacing and
lets AlertDialog drop its conditional sticky markup: the chrome classes
are now applied uniformly regardless of the sticky toggles.

Made-with: Cursor

* ui/AlertDialog: Use Stack for footer actions row

Made-with: Cursor

* ui/Dialog: Fix initial scroll-state attributes on open

`useOverlayScrollStateAttributes` now returns a callback ref instead of
reading a `RefObject` inside a `useLayoutEffect`. Base UI popups mount
their DOM lazily when the overlay opens, so the old effect ran before the
node existed and never re-ran; the scroll-state attributes were only set
on the first actual scroll. The callback-ref version fires the moment the
popup element is attached (and again if it's unmounted/remounted), so the
sticky separators reflect the correct state as soon as the dialog opens.

Made-with: Cursor

* ui/Dialog: Address self-review feedback on sticky chrome CSS

- Restore a 1px `border` separator (with matching `-1px` padding
  compensation) in place of the inset `box-shadow`, so the sticky chrome
  stays visible in `forced-colors` / Windows High Contrast mode where
  box-shadow is suppressed. Layout rhythm still matches trunk.
- Move the sticky chrome's extra block padding into `:has()`-gated rules,
  so a sticky header/footer that isn't the first/last popup child doesn't
  double the popup's own padding.
- Add `scroll-padding-block-*` on the popup when a sticky chrome is
  present, so Tab / `scrollIntoView` keeps focused descendants clear of
  the pinned chrome (WCAG 2.4.11 focus-not-obscured).
- Drop the always-on `will-change: transform`; it forced a permanent
  compositor layer for every open dialog.
- Bump the sticky `z-index` from 1 to 2 and note the popup's stacking
  context, so a single layer of consumer content can sit below the
  chrome without covering the separator.
- Note the cross-package coupling with `alert-dialog/popup.tsx` above
  the shared chrome classes.

Made-with: Cursor

* ui: Address self-review feedback on overlay scroll-state hook

- Generic over the scrollable element (`<T extends HTMLElement>`) so
  consumers get a precisely typed `ref` and `onScroll`. Both call sites
  pass `<HTMLDivElement>` explicitly for clarity.
- Pair the `ResizeObserver` with a `MutationObserver` on the container's
  `childList`, so direct children added after mount start being observed
  and `updateScrollAttributes` re-runs on each mutation. Deep descendant
  reflow still propagates up through the existing child observers.
- Fold the file-header block comment into the hook's JSDoc, document the
  callback-ref rationale, the Strict Mode / repeated-attachment behavior,
  the scroll-state contract exposed to the consumer `onScroll`, and the
  forward path via `@container scroll-state()`.

Made-with: Cursor

* Address round 2 self-review feedback

- Hoist popup scroll-padding to `:has(> .headerSticky)` / `:has(> .footerSticky)`
  so focused descendants stay visible even when the sticky chrome is not the
  first/last direct child of the popup.
- Bump scroll-padding from 5rem to 6rem for a safer upper bound at larger
  font sizes / multi-line titles, with a comment explaining a measured
  (ResizeObserver-driven) refinement can replace this if ever needed.
- Broaden the MutationObserver's addedNodes check from `instanceof HTMLElement`
  to `instanceof Element` so SVG/MathML direct children are tracked too.
- Unobserve removedNodes to avoid leaking entries into ResizeObserver's set
  across long-lived overlays.
- Update story copy: separator borders "appear" (instant toggle) rather than
  "fade in" — no CSS transition is in play.

Made-with: Cursor

* Drop popup scroll-padding while focus is inside sticky chrome

When a dialog or alert dialog opens with initial focus landing on a footer
action button (or when Tab moves focus to a close icon inside the sticky
header), the `scroll-padding-block-*` reserved on the popup would cause the
browser's focus scroll to move the already-pinned chrome out of the way,
scrolling the content unnecessarily.

Guard the padding with `:has(> .headerSticky:focus-within)` /
`:has(> .footerSticky:focus-within)` exceptions that reset it to 0 while
focus is inside the sticky chrome itself. When focus leaves into non-sticky
content, the padding reapplies and continues to keep focused descendants
clear of the pinned chrome.

Made-with: Cursor

* Drawer: Add sticky header and footer support

Extend the sticky chrome pattern already used by Dialog and AlertDialog to
`Drawer.Header` and `Drawer.Footer`. Both gain an optional `sticky` prop
(default `true`) that pins the chrome to the drawer's top/bottom edge while
the body scrolls. Separator borders only appear when there is off-screen
content in that direction, via the same data-attribute-driven approach
(`data-wp-ui-overlay-scrolled-*`) powered by the shared
`useOverlayScrollStateAttributes` hook wired into `Drawer.Popup`.

Drawer has a different internal structure from Dialog — `Drawer.Popup` is
the scroll container and `_Drawer.Content` carries the padding (including
per-swipe-direction safe-area overrides) — so the CSS mirrors the Dialog
approach but:

- Yields block padding on `.content` (not `.popup`) when sticky chrome is
  first/last child, and reclaims it on the chrome's own block padding.
- Preserves safe-area insets on up/down swipe-direction drawers by scoping
  the reclaim to `max(var(--wpds-dimension-padding-2xl), env(safe-area-*))`.
- Uses the shared 6rem `scroll-padding` + `:focus-within` exception so the
  browser's focus scroll doesn't fight pinned chrome.

Spacing for the non-sticky case is unchanged: the base `.header` /
`.footer` rules keep trunk's original `margin-bottom: gap-lg` and
`margin-top: gap-lg; padding-top: padding-lg`. The sticky overrides shave
1px off the matching direction to compensate for the transparent separator
border, so the visible rhythm matches trunk whether the separator is
colored or not.

A Scrollable story mirrors Dialog's, driving `sticky` on both subcomponents
from toggles kept in sync inside/outside the drawer.

Made-with: Cursor

* ui/Dialog,Drawer: Anchor close icon to end when title is visually hidden

Wrapping Title in VisuallyHidden applies position: absolute, which removes
the title from flex flow and neutralises `.title { margin-inline-end: auto }`.
Also anchor the close icon via `margin-inline-start: auto` (selected through
the existing data-wp-ui-*-close-icon attribute) so it remains pushed to the
inline end regardless of whether the title participates in the flex layout.

Made-with: Cursor

* Uniform alertdialog spacing, aligning it with dialog

* ui: Share overlay chrome across Dialog/AlertDialog/Drawer

Extracts a new `overlay-chrome.module.css` that centralizes the sticky
chrome primitives — `.header-chrome`, `.footer-chrome`, `.header-sticky`,
`.footer-sticky`, `.header`/`.footer` flex layouts, `.title` — and all
scroll-container behavior (yield-and-reclaim padding, data-attribute
driven separator coloring, focus-aware scroll-padding). All rules key
off `[data-wp-ui-overlay-scroll-container]`, which `useOverlayScrollStateAttributes`
now toggles on attach, so the shared CSS applies uniformly regardless of
which element actually scrolls.

Dialog and AlertDialog consume the shared primitives directly and lose
their duplicated rules. AlertDialog's heading also gains the shared
`.title` class so its color and GCD defenses match Dialog/Drawer.

For Drawer, scroll ownership moves from `_Drawer.Popup` to
`_Drawer.Content` (which already carries the inner padding and safe-area
insets). Base UI's swipe-dismiss-on-scroll-edge logic discovers the
scrollable element dynamically, so the move is transparent to it. The
content element gets `block-size: 100%` plus a viewport-minus-inset
`max-block-size` cap on up/down drawers so `is-auto` sizing still yields
a scrollable box. Drawer's default spacing now matches Dialog/AlertDialog
(symmetric `gap-lg` around chrome) — the explicit `padding-top` and
`margin-block` overrides on `.header`/`.footer` are no longer needed.

Class names migrated to kebab-case (`.header-sticky`, `.footer-sticky`)
as part of the same pass.

Made-with: Cursor

* ui/Dialog,Drawer: Add size controls to Scrollable stories

Extends the `Scrollable` stories with inline size knobs (and a swipe
direction knob on Drawer) so reviewers can exercise sticky chrome
against every popup variant without re-mounting the story. AlertDialog
and Popover are intentionally skipped: neither exposes a `size` prop.

Drawer's story switches from `args.children` to a `render` function so
state can drive both `Drawer.Popup size` and `Drawer.Root swipeDirection`.

Made-with: Cursor

* ui/AlertDialog: Address mirka's story + JSDoc feedback

- Scrollable story now passes `<ScrollableContent />` via `args.children`
  instead of hardcoding it inside a custom `render`, matching the other
  AlertDialog stories.
- Trim the `stickyHeader` / `stickyFooter` JSDocs to drop the separator
  border detail — that's a stylistic implementation detail, not part of
  the API contract.

Made-with: Cursor

* ui: Scope overscroll contain to modal overlays

Move `overscroll-behavior: contain` out of the per-component CSS and into
`overlay-chrome.module.css`, guarded by a new
`data-wp-ui-overlay-modal` attribute. Dialog and Drawer mirror their
`modal` prop onto the popup; AlertDialog sets it unconditionally since
it's always modal. Non-modal Dialog / Drawer no longer trap scroll,
which matches the intuition that a non-modal overlay lets the page
underneath remain scrollable.

The shared rule uses two selectors to cover both scroll-container
topologies in the codebase: one for Dialog/AlertDialog where the popup
is itself the scroll container, and one for Drawer where the scroll
container sits inside the popup (`_Drawer.Content`).

Made-with: Cursor

* ui: Move overlay scrolling onto dedicated Content subcomponent

Introduces `Dialog.Content` and `Drawer.Content` as the scroll-owning
region of each overlay, confining the scrollbar to the body area between
the pinned header and footer (Jay's request on #77559). The pinned-vs-
scrolls-with-body toggle for `Header` / `Footer` moves from a `sticky`
prop to pure DOM placement: render the chrome as a sibling of `Content`
to pin it, nest it inside `Content` to scroll with the body.

`AlertDialog` keeps `stickyHeader` / `stickyFooter` props because its
chrome is internal and not composable — the props now control sibling-
vs-nested placement around an internal scroll container.

Shared `overlay-chrome.module.css` centralizes the chrome + scroll-
container layout. The separator line lives on the pinned chrome (not
on `Content`) and is reserved via a `1px solid transparent` border on
the inner block side only when pinned; scroll state toggles the color.
The pinned chrome subtracts 1px from its adjacent padding so the
border-box height is identical whether pinned or not. Nested chrome
inside `Content` collapses its block-outer and inline padding to avoid
stacking on top of `Content`'s padding.

Storybook `Scrollable` stories for Dialog and Drawer expose a "Sticky
header / Sticky footer" toggle that flips the DOM placement at runtime.

PR description, CHANGELOG, and JSDoc updated to reflect the new API.

Made-with: Cursor

* ui: Loosen overlay chrome sibling selectors

Switches the shared overlay-chrome sibling rules from adjacent (`+`)
to general-sibling (`~`) combinators, so extra elements between
`.header` / `.content` / `.footer` (custom wrappers, an a11y live
region, etc.) don't silently break the pinned layout. Expected DOM
shape is still `header → content → footer`; intermediate siblings are
now tolerated.

Also splits the nested-chrome padding rules so `padding-inline: 0`
applies to any `.content > .header` / `.footer` regardless of its
position inside `.content`, while `padding-block-*: 0` stays guarded
by `:first-child` / `:last-child`. A nested header that isn't the
first child of `.content` (e.g. preceded by a `VisuallyHidden` shim)
still collapses inline padding correctly; block padding only
collapses at the scroll-edge position where it's semantically right.

Adds a forced-colors note explaining why the transparent separator
border still works in FC mode without extra rules: `transparent`
border-color is preserved per spec, and the toggled-in token color is
substituted with `CanvasText` by the UA.

Made-with: Cursor

* ui: Move Dialog/Drawer Content requirement to Breaking Changes

Dropping `overflow: auto` from `Dialog.Popup` / `Drawer.Popup` means
existing consumers that don't wrap body content in `Dialog.Content` /
`Drawer.Content` now silently clip instead of scrolling. That is a
behavior change and belongs under Breaking Changes, not Enhancements.
Keeps a short Enhancements line for the actual feature (pinned chrome
+ separator colorization) and points to the Breaking Changes entry
for the API details.

Made-with: Cursor

* ui: Make overflowing overlay scroll container keyboard-reachable

WCAG 2.1.1 requires scrollable regions to be reachable by keyboard.
`useOverlayScrollStateAttributes` now toggles `tabindex="0"` on its
target only while the element actually overflows; a non-scrolling
`<div tabindex="0">` is a tab-stop anti-pattern, so we remove it as
soon as the overflow disappears. A `data-wp-ui-overlay-scroll-tabbable`
flag marks hook-installed tabindex so a consumer-supplied `tabindex`
is never overwritten.

To keep the newly tabbable scroll container from stealing initial
focus when a real interactive control is available, widen
`useDeprioritizedInitialFocus` to accept a list of deprioritized
attributes, and include `data-wp-ui-overlay-scroll-container` in that
list from `Dialog.Popup` / `Drawer.Popup`. Popover keeps its single
close-icon attribute since it has no managed scroll container.

The `SCROLL_CONTAINER_ATTR` constant is exported from
`use-overlay-scroll-state-attributes` so the popups can reference it
without stringly-coupling to the library-internal data attribute.

Made-with: Cursor

* ui: Add tests for Dialog/Drawer/AlertDialog scroll container

Covers the JSDOM-reachable behavior of the new scroll-container
architecture: ref forwarding on the new `Content` subcomponent,
sibling-vs-nested chrome placement, `data-wp-ui-overlay-modal`
mirroring (including the non-modal and `trap-focus` cases where the
attribute must be absent), composed `onScroll`,
`data-wp-ui-overlay-scroll-container` target, and the overflow-driven
`tabindex` toggle (exercised by stubbing `scrollHeight` /
`clientHeight` / `scrollTop` on the element). AlertDialog also
verifies that `stickyHeader` / `stickyFooter` reparent the internal
chrome into or out of the scroller.

Purely style-dependent behavior — separator visibility, sticky
positioning, `overscroll-behavior`, the ResizeObserver-driven update
loop — isn't testable in JSDOM and stays out of scope.

Made-with: Cursor

* ui: Extend overlay scroll MutationObserver to the subtree

The previous `childList`-only MutationObserver at the scroll container
missed descendant mutations that don't propagate a resize up to a
direct child — e.g. rows appended to a nested list, a lazy component
mounting into a fixed-size wrapper. With the scroll container at
\`flex: 1 1 auto; min-block-size: 0\`, its own box doesn't resize as
content grows beyond the viewport, so the ResizeObserver alone
wouldn't refresh attributes either, and the newly overflowing region
could stay without a \`tabindex\` until the user happened to scroll.

Widen the MutationObserver to \`{ childList: true, subtree: true }\` so
any descendant addition / removal triggers an attribute refresh. The
callback still only re-observes ResizeObserver targets for direct
children — subtree observation is purely for discovery, keeping the
per-mutation cost O(1) regardless of tree depth.

Made-with: Cursor

* ui: Clip inline overflow on overlay Content; document block-only scrolling

Overlay popups don't support horizontal scroll: the chrome layout is
block-axis only, and `useOverlayScrollStateAttributes` tracks overflow
on the block axis alone. Make that contract explicit in CSS by adding
`overflow-inline: hidden` to the shared `.content` scroll container,
and call it out in the hook's JSDoc so consumers know to constrain
content width rather than expect a horizontal scrollbar.

No visual change in practice — the popup's own `overflow: hidden`
already clipped wide content; this just moves the clip to the scroll
container where the intent is explicit and the behavior doesn't
accidentally rely on the popup's clipping.

Made-with: Cursor

* ui: Tighten overlay scroll utility docs, types, and helpers

Bundled small follow-ups from self-review:

- \`useOverlayScrollStateAttributes\`: document the
  consumer-pre-set-tabindex edge case inline so the trade-off around
  the \`data-wp-ui-overlay-scroll-tabbable\` flag is obvious, and
  comment the cleanup-path guard that uses it.
- \`useDeprioritizedInitialFocus\`: drop the \`readonly\` on
  \`deprioritizedAttributes\` (all call sites pass plain arrays) and
  update the inline comment to call out that Base UI wraps
  \`initialFocus\` in \`useValueAsRef\`, so reference identity doesn't
  matter and skipping \`useMemo\` is intentional.
- \`AlertDialog\` test \`findScroller\`: distinguish "popup ref not
  attached" from "scroller not inside popup" for faster debugging
  when a test setup is wrong.
- \`CHANGELOG\`: make the sticky-chrome entries concise — one line
  for the breaking change, one line for the enhancement.

Made-with: Cursor

* ui: Add overlay scroll-state attribute + DOM-order tests

- Add a scroll-state attribute test per component
  (Dialog / Drawer / AlertDialog) that stubs \`scrollHeight\` /
  \`clientHeight\` / \`scrollTop\` and dispatches scroll events,
  asserting \`data-wp-ui-overlay-scrolled-from-top\` and
  \`data-wp-ui-overlay-scrolled-from-bottom\` toggle correctly as
  the simulated scroll position moves from top → middle → bottom.
- Tighten the pinned-chrome tests for Dialog and Drawer by also
  asserting the header comes *before* the scroll container in
  document order via \`compareDocumentPosition\` — the CSS
  sibling-based separator rules depend on that.

No JSDOM workarounds we can avoid here: the harness doesn't compute
layout, so initial-mount overflow detection can't be covered without
stubbing. Attribute-toggle + DOM-order coverage is the best fidelity
available without switching to a real-browser runner.

Made-with: Cursor

* ui: Surface tabIndex as an explicit Content prop with JSDoc

\`Dialog.Content\` and \`Drawer.Content\` used to inherit \`tabIndex\`
implicitly from \`ComponentProps<'div'>\`, which meant the
auto-tabbable-when-overflowing behavior and the consumer-override
edge case were only documented in the hook itself. Re-declare
\`tabIndex\` explicitly on both \`ContentProps\` types with JSDoc
that spells out:

- the automatic \`tabindex="0"\` that the component installs on
  overflow (WCAG 2.1.1);
- that an explicit \`tabIndex\` is never overwritten, including
  \`tabIndex={ -1 }\` as a deliberate opt-out;
- the single edge case: removing an explicit \`tabIndex\` at
  runtime lets the component take management back over, because
  a prior explicit value can't be distinguished from "unset".

No runtime change — this is purely documentation surfaced in
IDE tooltips and generated docs.

Made-with: Cursor

* ui: Apply shared focus-visible ring to overlay scroll containers

When the body overflows, the scroll container becomes a keyboard tab
stop so users can arrow-scroll it — but without a focus ring, there
was no visible indication that focus had landed there. Compose the
shared \`outset-ring--focus-visible\` utility onto \`Dialog.Content\`,
\`Drawer.Content\`, and the AlertDialog internal scroll container so
the ring matches the color, width, and transition used by every
other focusable surface in the design system.

Made-with: Cursor

* ui: Inset the overlay scroll container focus ring

The shared \`outset-ring--focus-visible\` utility offsets the ring
\`+1px\` outside the element. For the overlay scroll container, that
puts the ring at the popup's rounded corners, where \`overflow:
hidden\` on the popup clips it. Override \`outline-offset\` to a
negative value (keyed off \`--wpds-border-width-focus\` so it tracks
the token) in the shared \`.content\` rule so the ring sits flush
inside the content box and stays fully visible within the popup's
clipping region. All other visual properties (color, width,
transition) still come from the shared utility.

Made-with: Cursor

* ui/AlertDialog: Deprioritize scroll container in initial focus

When `AlertDialog` body content overflows, the internal scroll
container becomes `tabindex="0"` so keyboard users can arrow-scroll it
(WCAG 2.1.1). Without `useDeprioritizedInitialFocus` wired up, that
container would be the first tabbable element in the popup at open
time, stealing initial focus from the Cancel/OK actions.

Mirror the wiring already present on Dialog and Drawer: route
`_AlertDialog.Popup` through `useDeprioritizedInitialFocus` with
`[SCROLL_CONTAINER_ATTR]` (no close icon to skip on AlertDialog) and
forward the resolved callback as `initialFocus`. Add a regression
test that forces the scroller into the overflowing state via
`Element.prototype` getter stubs and asserts focus settles on the
Cancel button.

Made-with: Cursor

* ui: Drop subtree observation on overlay MutationObserver

Round 4's switch to `subtree: true` was meant to catch async content
added deep inside the scroll region (rows in a nested list, a lazy
component mounting into a wrapper that doesn't itself resize, …).
In practice, anything whose growth actually changes the scroll size
already propagates a resize up the layout tree and is caught by the
existing `ResizeObserver` on direct children — and `subtree: true`
fans the callback out over every text-node insertion in
content-heavy overlays (rich-text editors, virtualized lists), which
isn't worth the cost of the rare deep-mutation-without-resize case.

Revert to `{ childList: true }` only and document the trade-off in
the hook's JSDoc, calling out rAF-coalescing as the future
mitigation if a real consumer ever needs the broader observation.

Made-with: Cursor

* ui: Polish overlay scroll utility internals

Two small cleanups that don't change behavior:

- Loop over a `HOOK_OWNED_ATTRS` constant in `cleanupScrollAttributes`
  instead of three open-coded `removeAttribute` calls, so future
  attributes added to the hook don't have to remember to extend the
  cleanup path.
- Annotate `SCROLL_TABBABLE_FLAG_ATTR` with a note that its literal
  string is named in the public JSDoc as a debugging breadcrumb, so
  anyone tempted to rename it remembers to update the JSDoc too.

Made-with: Cursor

* ui: Clarify why the inset focus-ring offset is static

Rephrase the existing comment block on the overlay scroll
container's `:focus-visible` rule so it reads as cause →
consequence: the shared utility's `+1px` outline-offset would
collide with the popup's rounded clipping region, so we override to
a negative offset that sits inside the content box.

Also call out that the shared utility's `transition: outline …`
shorthand only animates outline-color and outline-width, not
outline-offset — so the negative offset applied here is static by
design, with the visible fade-in/out coming from color change
only. Future maintainers seeing "non-transitioned offset on top of
a transitioned outline" won't need to re-derive the trade-off.

Made-with: Cursor

* ui: Document the role-less scroll region on Content tabIndex JSDoc

Extend the JSDoc on `Dialog.ContentProps` and `Drawer.ContentProps`
`tabIndex` to call out that the scroll region intentionally renders
without `role` / `aria-label`. The auto-managed `tabindex="0"` makes
the region keyboard-reachable when it overflows (WCAG 2.1.1), but a
generic landmark role announcement would land on top of the dialog's
own heading + body context and be redundant for screen-reader users.

Surfaces the trade-off in the same place a consumer would see when
deciding whether to override `tabIndex` themselves.

Made-with: Cursor

* ui: Detect consumer takeover of hook-managed scroll-container tabindex

If the hook installed `tabindex="0"` on an overflowing scroll container
and the consumer later passed an explicit `tabIndex` prop, the internal
"this is ours" flag stayed in place. A subsequent non-overflow cleanup
tick would then strip the consumer's value, contradicting the contract
documented on `Dialog.Content` / `Drawer.Content`.

Reconcile at the start of every update and cleanup: when the flag is
set but the current `tabindex` is no longer `"0"`, drop just the flag
and leave the consumer's value alone. Add a regression test that
flips overflow on, mutates the attribute to simulate takeover, then
flips overflow off and asserts the consumer's value survives.

Made-with: Cursor

* ui: Document the overlay scroll-state tabindex contract explicitly

Tighten the JSDoc on `useOverlayScrollStateAttributes` so the tabindex
contract reads as a single labelled list rather than scattered prose:

- Pre-install opt-out (consumer sets `tabindex` before first overflow).
- Takeover after install (consumer overrides the hook's `"0"` later).
- Indistinguishable case (`tabIndex={ 0 }` collides with the managed
  value, so the hook can't tell them apart and may strip it on the
  next non-overflow tick).

Also reword the inline comments in `updateScrollAttributes` so each
comment block sits next to the code it describes (the takeover note
moves up next to the `reconcileTabbableFlag` call; the pre-install
opt-out note stays with the install branch). And expand the
`reconcileTabbableFlag` JSDoc to cross-reference the contract.

Comment-only change.

Made-with: Cursor

* ui: Note the cleanup-path coverage on the tabindex takeover test

Document why the takeover regression test exercises only
`updateScrollAttributes` and not `cleanupScrollAttributes`: both
paths share `reconcileTabbableFlag`, so a single test guards both.
Flag the maintenance condition (split / inline the helper → add an
explicit unmount-after-takeover test) so future readers don't have
to re-derive the coverage rationale.

Comment-only change.

Made-with: Cursor

* ui: Update Dialog/Drawer Content tabIndex JSDoc with full contract

Reflect the takeover-after-install support and the
`tabIndex={ 0 }` indistinguishable case in the public JSDoc on
`Dialog.Content` and `Drawer.Content`. The wins-and-is-never-
overwritten guarantee now explicitly covers overrides applied after
the component had already managed the value, and a short bullet on
the `0`-collision corner case nudges consumers toward picking a
different value when they need the tabindex to stick across
overflow flips.

Doc-only change.

Made-with: Cursor

---

Co-authored-by: ciampo <mciampini@git.wordpress.org>
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: jameskoster <jameskoster@git.wordpress.org>
Co-authored-by: tyxla <tyxla@git.wordpress.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Package] UI /packages/ui [Type] Enhancement A suggestion for improvement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

UI: Make Dialog.Header and Dialog.Footer sticky

5 participants