UI/Dialog: deprioritize close icon for initial focus#76910
Conversation
|
Size Change: 0 B Total Size: 7.73 MB ℹ️ View Unchanged
|
|
Flaky tests detected in 1b7a7f3. 🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/23808321092
|
|
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 If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
|
(@sethrubenstein this is relevant also for the block.) |
|
@carolinan @sethrubenstein I'm not sure which "block" you're referring to, but to clarify, this component is not for an end-user-facing dialog; it's for the editor and admin interfaces. To add some extra context, this change affects the |
|
Yes, that is clear, but the same, avoid focusing on the close icon by default (if there are other focusable elements) should be considered for the dialog block. |
Default initialFocus now skips elements marked with data-wp-ui-popover-close, focusing the first tabbable content element instead. Falls back to the close button when it is the only tabbable element. Mirrors the Dialog.Popup approach from #76910. Made-with: Cursor
aduth
left a comment
There was a problem hiding this comment.
Behavior-wise works as expected 👍 Review comments are largely concerned with implementation details but not especially blocking.
Following WAI-ARIA APG guidance, Dialog.Popup's default initialFocus now skips the close icon button and focuses the first tabbable content element instead. The close icon remains focusable via Tab and is used as a fallback when no other tabbable element exists. Implementation replicates Base UI's defaultInitialFocus logic (including touch interaction handling) with one tweak: elements marked with data-wp-dialog-close-icon are deprioritized. Uses the tabbable library (same as Base UI) for robust tabbable element detection. Made-with: Cursor
Move the initial-focus deprioritization logic into a reusable hook at utils/use-deprioritized-initial-focus. The hook returns a resolved initialFocus value that skips elements marked with a given data attribute (e.g. a close icon), and a popupRef that the consumer merges onto the popup element. Addresses review feedback from #76910: - Fix @see URL to point to the correct floating-ui source - Use early return when popupRef is null (improved readability) - Define InteractionType locally to avoid private Base UI imports Includes unit tests covering passthrough, touch interaction, non-touch deprioritization, fallback, and unattached ref scenarios. Made-with: Cursor
Replace inline initial-focus logic with the shared hook. The hook owns the popupRef; the component merges it with the forwarded ref. Made-with: Cursor
Made-with: Cursor
106c4d5 to
d2241c6
Compare
Replace the manually defined InteractionType and InitialFocus types with a single type alias derived from Dialog.Popup.Props['initialFocus']. Made-with: Cursor
Default initialFocus now skips elements marked with data-wp-ui-popover-close, focusing the first tabbable content element instead. Falls back to the close button when it is the only tabbable element. Mirrors the Dialog.Popup approach from #76910. Made-with: Cursor
* UI: Dialog smart default focus — deprioritize close icon Following WAI-ARIA APG guidance, Dialog.Popup's default initialFocus now skips the close icon button and focuses the first tabbable content element instead. The close icon remains focusable via Tab and is used as a fallback when no other tabbable element exists. Implementation replicates Base UI's defaultInitialFocus logic (including touch interaction handling) with one tweak: elements marked with data-wp-dialog-close-icon are deprioritized. Uses the tabbable library (same as Base UI) for robust tabbable element detection. Made-with: Cursor * update package-lock * Rename data attribute * CHANGELOG PR number * Extract useDeprioritizedInitialFocus shared hook Move the initial-focus deprioritization logic into a reusable hook at utils/use-deprioritized-initial-focus. The hook returns a resolved initialFocus value that skips elements marked with a given data attribute (e.g. a close icon), and a popupRef that the consumer merges onto the popup element. Addresses review feedback from #76910: - Fix @see URL to point to the correct floating-ui source - Use early return when popupRef is null (improved readability) - Define InteractionType locally to avoid private Base UI imports Includes unit tests covering passthrough, touch interaction, non-touch deprioritization, fallback, and unattached ref scenarios. Made-with: Cursor * Dialog.Popup: use shared useDeprioritizedInitialFocus hook Replace inline initial-focus logic with the shared hook. The hook owns the popupRef; the component merges it with the forwarded ref. Made-with: Cursor * CHANGELOG: add shared hook entry Made-with: Cursor * Derive InitialFocus type from Base UI's public API Replace the manually defined InteractionType and InitialFocus types with a single type alias derived from Dialog.Popup.Props['initialFocus']. --- Co-authored-by: ciampo <mciampini@git.wordpress.org> Co-authored-by: aduth <aduth@git.wordpress.org> Co-authored-by: carolinan <poena@git.wordpress.org>
Default initialFocus now skips elements marked with data-wp-ui-popover-close, focusing the first tabbable content element instead. Falls back to the close button when it is the only tabbable element. Mirrors the Dialog.Popup approach from #76910. Made-with: Cursor
Default initialFocus now skips elements marked with data-wp-ui-popover-close, focusing the first tabbable content element instead. Falls back to the close button when it is the only tabbable element. Mirrors the Dialog.Popup approach from #76910. Made-with: Cursor
* UI: Dialog smart default focus — deprioritize close icon Following WAI-ARIA APG guidance, Dialog.Popup's default initialFocus now skips the close icon button and focuses the first tabbable content element instead. The close icon remains focusable via Tab and is used as a fallback when no other tabbable element exists. Implementation replicates Base UI's defaultInitialFocus logic (including touch interaction handling) with one tweak: elements marked with data-wp-dialog-close-icon are deprioritized. Uses the tabbable library (same as Base UI) for robust tabbable element detection. Made-with: Cursor * update package-lock * Rename data attribute * CHANGELOG PR number * Extract useDeprioritizedInitialFocus shared hook Move the initial-focus deprioritization logic into a reusable hook at utils/use-deprioritized-initial-focus. The hook returns a resolved initialFocus value that skips elements marked with a given data attribute (e.g. a close icon), and a popupRef that the consumer merges onto the popup element. Addresses review feedback from #76910: - Fix @see URL to point to the correct floating-ui source - Use early return when popupRef is null (improved readability) - Define InteractionType locally to avoid private Base UI imports Includes unit tests covering passthrough, touch interaction, non-touch deprioritization, fallback, and unattached ref scenarios. Made-with: Cursor * Dialog.Popup: use shared useDeprioritizedInitialFocus hook Replace inline initial-focus logic with the shared hook. The hook owns the popupRef; the component merges it with the forwarded ref. Made-with: Cursor * CHANGELOG: add shared hook entry Made-with: Cursor * Derive InitialFocus type from Base UI's public API Replace the manually defined InteractionType and InitialFocus types with a single type alias derived from Dialog.Popup.Props['initialFocus']. --- Co-authored-by: ciampo <mciampini@git.wordpress.org> Co-authored-by: aduth <aduth@git.wordpress.org> Co-authored-by: carolinan <poena@git.wordpress.org>
Default initialFocus now skips elements marked with data-wp-ui-popover-close, focusing the first tabbable content element instead. Falls back to the close button when it is the only tabbable element. Mirrors the Dialog.Popup approach from #76910. Made-with: Cursor
* Add Popover component to @wordpress/ui
Introduce a new Popover primitive wrapping Base UI's Popover.
Includes Root, Trigger, Popup, Arrow, Title, Description, and Close
subcomponents following existing package conventions.
Made-with: Cursor
* Add Storybook stories for Popover component
Include stories for default usage, positioning, close button,
controlled mode, modal behavior, and custom z-index.
Made-with: Cursor
* Add unit tests for Popover component
Cover ref forwarding for all subcomponents, open/close behavior,
controlled mode, defaultOpen, onOpenChange callback, and
accessibility attributes.
Made-with: Cursor
* Fix review issues in Popover component
- Add children prop to ArrowProps for custom arrow content
- Fix invalid CSS token: use --wpds-color-fg-content-neutral-weak
instead of non-existent --wpds-color-fg-content-neutral-secondary
Made-with: Cursor
* Extend Popover Popup with additional props
- initialFocus / finalFocus: custom focus behavior on open/close
- variant: 'default' | 'unstyled' for visual style control
- inline: render without portal
- collisionAvoidance / collisionBoundary / collisionPadding / sticky:
fine-grained collision handling (replaces legacy flip/resize/shift)
- container: portal target for cross-document rendering
Made-with: Cursor
* Add stories and tests for new Popover props
Stories:
- Unstyled variant
- Overlay placement (useMeasure technique with negative sideOffset)
- Disabled animations (data-instant CSS selector)
- Inline rendering (no portal)
- Collision avoidance
- Cross-iframe portal (container prop)
Tests:
- Unstyled variant strips popup class
- Inline renders within parent container
- Portal renders outside parent container (default)
- initialFocus={false} prevents focus movement
Made-with: Cursor
* Fix Controlled story race condition
Replace toggle button with explicit open/close buttons to avoid
the click-outside dismiss racing with the toggle handler.
Made-with: Cursor
* Improve Modal story with interactive form content
Replace static text with a name/email form to better demonstrate
the focus-trapping behavior described in the story's JSDoc.
Made-with: Cursor
* Fix Controlled story click-outside flicker
Ignore dismiss events whose click target is inside the external
controls wrapper, preventing the close-then-reopen flicker.
Made-with: Cursor
* Simplify Controlled story with checkbox toggle
Replace the button-based external controls with a checkbox that
drives the popover state. This avoids the click-outside dismiss
race entirely and makes the controlled pattern clearer.
Made-with: Cursor
* Use Popover.Title and Description consistently in stories
Add missing Title/Description to Unstyled and CollisionAvoidance
stories for better accessibility and consistent usage patterns.
Made-with: Cursor
* Fix animations and add `animated` prop to Popover.Popup
Move the dropdown-motion CSS class from the Positioner to the Popup
element, where Base UI places the `data-starting-style` attribute
needed for enter transitions.
Add an `animated` prop (default `true`) so consumers can disable
animations declaratively instead of needing to know about the
`data-instant` implementation detail.
Made-with: Cursor
* Show all side × align combinations in Positioning story
Display all 12 positioning combinations (4 sides × 3 aligns) in a
grid, all open by default with animations and collision avoidance
disabled so the layout stays stable.
Made-with: Cursor
* Mirror legacy WithSlotOutsideIframe pattern in CrossIframe story
Add scrollable iframe content (200vh) so the popover's cross-document
anchor tracking can be verified by scrolling. Match the legacy
example's layout with a centered salmon-colored trigger and the
popover open by default.
Made-with: Cursor
* Fix require error in GenericIframe by using static import
Replace runtime `require('@wordpress/element').createPortal` with the
already-imported `createPortal` from `@wordpress/element`.
Made-with: Cursor
* Add CrossIframeWithSlotFill story demonstrating SlotFill pattern
Create a minimal SlotFill using React context that shares a container
ref from the parent document. The popover inside the iframe reads the
slot via usePopoverSlot() and passes it to the container prop, showing
how the pattern works without manual ref plumbing.
Made-with: Cursor
* Use @wordpress/components SlotFill in CrossIframeWithSlotFill story
Replace the custom React context SlotFill with the real
SlotFillProvider and Slot from @wordpress/components. The Slot renders
a div in the parent document whose ref is passed to the Popover.Popup
container prop, mirroring the legacy WithSlotOutsideIframe pattern.
Made-with: Cursor
* Set collisionBoundary in collision avoidance and cross-iframe stories
Portal'd popovers don't inherit scrollable ancestors as clipping
boundaries. Pass the scrollable container (or iframe element) as
collisionBoundary so Floating UI correctly flips/clips against it.
Also make GenericIframe forward refs so the iframe element can be
captured for use as a collision boundary.
Made-with: Cursor
* Use args and disable hardcoded controls in Popover stories
- Positioning, CollisionAvoidance: disable all controls (multi-popover
demos where args don't apply meaningfully)
- Controlled: disable open/onOpenChange/defaultOpen (managed by
internal state)
- Modal: pass modal via args, disable its control
- OverlayPlacement, CrossIframe, CrossIframeWithSlotFill: pass
defaultOpen via args, disable its control
Made-with: Cursor
* Improve Controlled and Modal stories args handling
- Controlled: move children to args, spread all args on Root, fix
onOpenChange to filter outside-press/focus-out from checkbox controls
- Modal: convert to pure args (no render function), use hardcoded IDs
since hooks can't be used in args
- Default: use generic placeholder text
Made-with: Cursor
* Refactor Popover stories: args forwarding, utils extraction, z-index fix
- Forward all args to Popover.Root in every render-based story
(OverlayPlacement, CrossIframe, CrossIframeWithSlotFill) so
controls are wired to the live component
- Disable children control globally in meta (re-enable for Default
only), since children JSX is not meaningfully editable via controls
- Extract useMeasure and GenericIframe to stories/utils.tsx with
JSDoc comments
- Fix WithCustomZIndex to actually set --wp-ui-popover-z-index via
a scoped style tag and className on the positioner
Made-with: Cursor
* Align WithCustomZIndex story with Select component pattern
Use inline style prop to set --wp-ui-popover-z-index, matching how
Select.Popup sets --wp-ui-select-z-index. Removes the unnecessary
scoped <style> tag and className approach.
Made-with: Cursor
* Expose `anchor` prop on Popover.Popup
Pick the `anchor` prop from Base UI's Positioner and forward it in
popup.tsx. This enables anchoring to an arbitrary element, virtual
element, ref object, or callback — the most-used pattern in Gutenberg
(block popovers, link popover, data views context menus).
Made-with: Cursor
* Expose `openOnHover`, `delay`, and `closeDelay` props on Popover.Root
Pick hover-trigger props from Base UI's Root. This enables popover
opening on hover with configurable timing — a capability the legacy
Popover does not have natively.
Made-with: Cursor
* Add Popover.Backdrop sub-component
Expose an opt-in backdrop overlay for modal popovers. Two variants:
- 'default': semi-transparent dark overlay with fade transition
- 'unstyled': transparent click-blocking layer without visual dimming
Made-with: Cursor
* Alias Base UI positioner CSS variables with --wp-ui-popover-* prefix
Add aliased CSS custom properties in .positioner for all 7 Base UI
Positioner variables (anchor size, available space, positioner size,
transform origin). Update item-popup.module.css to use the aliased
names with fallbacks for backward compatibility.
Made-with: Cursor
* Add Storybook stories for new Popover features
Add 7 new stories demonstrating:
- Anchor (4 anchor types: element, virtual, ref, callback)
- Toolbar Variant (unstyled + custom CSS)
- Viewport-Constrained Size (--wp-ui-popover-available-height/width)
- onOpenChange Details (event reason logging)
- Initial Focus (custom focus target via ref)
- Trap Focus (modal='trap-focus' for constrainTabbing equivalent)
- Hover Trigger (openOnHover with delay/closeDelay)
Update Modal story to include Popover.Backdrop and register Backdrop
in Storybook subcomponents.
Made-with: Cursor
* Fix: move openOnHover/delay/closeDelay to TriggerProps
These props belong on Popover.Trigger, not Popover.Root — they are
defined on Base UI's PopoverTrigger.Props. Picking them from Root.Props
caused TS2344 build failures in CI.
Update the HoverTrigger story to pass the props to Popover.Trigger.
Made-with: Cursor
* Fix CHANGELOG.md placeholder PR number
Replace #XXXXX with the actual PR number #76438.
Made-with: Cursor
* Remove built-in animation from Popover
Per team consensus, this low-level primitive should not provide a
default animation — different popover use cases (tooltips, dropdowns,
dialogs, drawers) require different animations, and a unified approach
needs design exploration first.
Remove the `animated` prop, `dropdown-motion` class usage, the
`DisabledAnimations` story, and the related test.
Made-with: Cursor
* Remove CSS variable aliases, use Base UI variables directly
Per team consensus, don't alias Base UI's Positioner CSS variables with
a --wp-ui-popover-* prefix. Use the upstream names (--available-height,
--anchor-width, etc.) directly — gating them adds maintenance overhead
with no practical benefit since data attributes already can't be gated.
Retain --wp-ui-popover-z-index which is our own custom variable.
Made-with: Cursor
* Remove Popover.Backdrop sub-component
Per review feedback, the standalone Backdrop will be removed and its
functionality merged into Popover.Popup (via a showBackdrop prop) in
a follow-up. Base UI already renders an invisible internal backdrop for
modal popovers to capture clicks, so the visual overlay is opt-in.
Made-with: Cursor
* Add `backdrop` prop to Popover.Popup
Render the backdrop inside the Popup component (matching the Dialog
pattern) instead of exposing a standalone Backdrop sub-component.
When `backdrop` is true, a semi-transparent overlay renders behind the
popover inside the portal. Typically used with `modal` to signal that
page interaction is blocked.
Made-with: Cursor
* Fix Anchor story VirtualElement positioning in docs mode
Derive the virtual anchor position from its label div's actual
bounding rect instead of hard-coded viewport coordinates. This
prevents the popover from rendering at a fixed position that
overlaps other stories in Storybook's docs page.
Made-with: Cursor
* Popover.Popup: deprioritize close button for initial focus
Default initialFocus now skips elements marked with
data-wp-ui-popover-close, focusing the first tabbable content element
instead. Falls back to the close button when it is the only tabbable
element. Mirrors the Dialog.Popup approach from #76910.
Made-with: Cursor
* Extract useDeprioritizedInitialFocus into shared utility
Move the initial-focus deprioritization logic (skip close button, fall
back gracefully) into a reusable hook at utils/use-deprioritized-initial-focus.
Popover.Popup now consumes this hook. The hook is designed to be reused
by Dialog and other overlay components in a follow-up.
Made-with: Cursor
* Update CHANGELOG: move Popover to Unreleased, add shared hook entry
Made-with: Cursor
* Popover: use Text for Title/Description, require Title for accessibility
- Popover.Title now renders via Text with the heading-md variant, and
Popover.Description via Text with the body-md variant. Typography CSS
removed from style.module.css in favor of the Text component's tokens.
- Add PopoverValidationProvider (dev-only) that throws when Popover.Title
is missing or empty, mirroring Dialog's validation pattern.
- Add Popover.Title to all existing tests and stories. Stories that have
no visible heading use VisuallyHidden inside Popover.Title.
Made-with: Cursor
* Popover: enrich JSDoc across all subcomponents
Make Root's JSDoc a full component overview with sub-component listing
and usage example. Add detailed descriptions to Trigger, Popup, Arrow,
Title, Description, and Close so Storybook can source docs from JSDoc
instead of manual `parameters.docs` config.
Made-with: Cursor
* Popover: drop Storybook parameters.docs in favor of JSDoc
Remove the manual `parameters.docs.description.component` from story
meta — Storybook now sources the component description directly from
the Root component's JSDoc.
Made-with: Cursor
* Popover: derive InteractionType from public API, document style/className split
Replace the fragile `InteractionType` import from the private
`@base-ui/utils/useEnhancedClickHandler` path with types derived from
the public `_Popover.Popup.Props['initialFocus']` type.
Add interface-level JSDoc on `PopupProps` documenting that `style` and
`className` are forwarded to the Positioner element (for z-index and
CSS variable access) while other HTML attributes target the inner Popup.
Made-with: Cursor
* Popover: fix ViewportConstrainedSize story, improve useMeasure
Move `overflow: auto` from the Popup's style prop (which targets the
Positioner) into an inner wrapper div where scrolling belongs. Update
the story JSDoc to explain the pattern.
Improve `useMeasure` to perform a synchronous `getBoundingClientRect`
read in the ref callback, eliminating the 0×0 flash on first render.
Made-with: Cursor
* Popover: add TODO for backdrop color token
Add inline TODO noting the hardcoded rgba backdrop color should be
replaced with a WPDS overlay/scrim token when one is introduced.
Made-with: Cursor
* Popover: add PopupProps JSDoc documenting style/className split
The interface-level JSDoc was lost during a previous pre-commit
formatting pass. Re-adds the documentation explaining that style and
className target the Positioner element while other attributes go to
the inner Popup.
Made-with: Cursor
* Popover.Arrow: render default arrow SVG with proper positioning
Base UI's Arrow renders an empty container — the visual shape must
be provided as a child. Add a default SVG arrow (fill + stroke paths)
that matches the popup background and border via design tokens. Add
side-aware positioning and rotation styles. Consumers can still
override by passing custom children.
Made-with: Cursor
* Popover.Popup: add border, increase default sideOffset
Add a subtle border using design tokens to match the arrow stroke.
Increase default sideOffset from 4 to 8 to give the arrow more room.
Made-with: Cursor
* CHANGELOG: remove redundant enhancement entry for new component
The initial-focus deprioritization is part of the Popover's initial
implementation, not a separate enhancement to an existing component.
Made-with: Cursor
* Fix overlay storybook example
* Popover stories: add NoArrow example
Made-with: Cursor
* Popover.Popup: expose arrowPadding prop, default to 8
Add arrowPadding to the Pick list in PopupProps and forward it to the
Positioner. The default of 8 prevents the arrow from overlapping the
popup's rounded corners (border-radius-md = 4px).
Made-with: Cursor
* Popover: increase popup padding to lg, update title variant and margin
- Change .popup padding from --wpds-dimension-padding-md to
--wpds-dimension-padding-lg (16px) per design feedback.
- Remove bottom margin from .title — consumers add spacing via Stack
or inline styles, avoiding unintended layout from visually hidden titles.
- Update Popover.Title Text variant from heading-md to heading-xl to
match Dialog.Title styling.
Made-with: Cursor
* Popover stories: update WithCloseButton, add title spacing, document focus trapping
- WithCloseButton: use IconButton with close icon and proper equidistant
positioning, matching the Dialog close-icon pattern.
- Add inline marginBottom to visible Popover.Title elements followed by
content, since the component no longer has built-in margin.
- Document that focus trapping (modal={true} and modal="trap-focus")
requires a Popover.Close inside the popup for screen reader escape.
- Add Popover.Close to TrapFocus story to enable focus cycling.
Made-with: Cursor
* Popover stories: replace hardcoded spacing with WPDS gap tokens
Use --wpds-dimension-gap-xs (4px) and --wpds-dimension-gap-sm (8px)
instead of hardcoded numeric values for margins and gaps.
Made-with: Cursor
* Popover stories: add InfoTip example
Demonstrate the recommended pattern for info-icon popups that open on
hover: use Popover with openOnHover instead of Tooltip, ensuring touch
and screen reader users can access the content.
Made-with: Cursor
* Popover stories: use plain button with Icon in InfoTip example
IconButton renders an internal Tooltip that conflicts with the
popover's openOnHover behavior. Replace with a plain button
containing an Icon and a VisuallyHidden label.
Made-with: Cursor
* Popover stories: use aria-label for InfoTip trigger
Replace VisuallyHidden with aria-label on the icon-only button —
the idiomatic approach for icon-only buttons.
Made-with: Cursor
* Popover stories: fix InfoTip trigger focus outline
Replace `all: unset` with targeted property resets so the native
browser focus outline is preserved on keyboard navigation.
Made-with: Cursor
* Popover stories: use WPDS border-radius-sm token for InfoTip trigger
Made-with: Cursor
* Popover stories: use WPDS cursor-control token for InfoTip trigger
Made-with: Cursor
* Popover tests: strengthen accessibility and variant assertions
- Accessibility tests now verify the popup element has aria-labelledby
and aria-describedby pointing at the Title/Description ids, not just
that the ids exist.
- Unstyled variant test compares against the default variant's class
list instead of asserting an empty className string, making it
resilient to Base UI adding runtime classes.
Made-with: Cursor
* Popover tests: improve matchers and async query patterns
- Use `toBeVisible()` instead of `toBeInTheDocument()` when asserting
popover content is shown — stronger assertion that checks visibility.
- Replace `waitFor` + `getBy*` with `findBy*` for cleaner async waits.
- Prefer `findByRole` over `findByText` where a semantic role exists.
- Store queried elements in variables to avoid redundant lookups.
Made-with: Cursor
* Popover: derive InitialFocus type from Popover, document modal focus trapping
- Replace `Dialog.Popup.Props['initialFocus']` with
`Popover.Popup.Props['initialFocus']` in the shared
`useDeprioritizedInitialFocus` hook, removing the Dialog import.
- Add JSDoc for the `modal` prop on `RootProps` explaining each value
and the requirement to include `Popover.Close` for focus cycling.
- Update `Popup` component JSDoc with the same guidance.
Made-with: Cursor
* Popover: move modal/focus-trapping docs from Popup to Root
The modal prop lives on Root, so the requirement for Popover.Close
when focus trapping is active belongs in Root's JSDoc, not Popup's.
Made-with: Cursor
* Popover: pick modal prop directly from Base UI's Root.Props
Base UI's own JSDoc already documents the modal values and the
Popover.Close requirement, so no need to duplicate it here.
Made-with: Cursor
* CHANGELOG: move Popover entry to Unreleased section
The Popover primitive hasn't shipped yet — move it out of the 0.10.0
released section into Unreleased.
Made-with: Cursor
* Popover.Arrow: remove unnecessary inline SVG styles
fillRule, clipRule, strokeLinejoin, and strokeMiterlimit were SVG
editor export artifacts with no visual effect on the simple arrow
paths.
Made-with: Cursor
* Popover.Popup: document arrowPadding default matching border-radius
Made-with: Cursor
* fix CHANGELOG order
* Popover: remove redundant margins, explicit render/ref forwarding
- Remove `margin: 0` from `.title` and `.description` CSS — now
handled by the `Text` component after #76970.
- Remove the empty `.title` CSS block and its references in title.tsx.
- Explicitly destructure and forward the `render` prop in both
Title and Description, making the consumer API intentional.
- Move `ref` to the outer `Text` component, letting Base UI's
`useRender` forward it through the composition chain.
- Document the `<VisuallyHidden render={<Popover.Title />}>` pattern
in the Title JSDoc.
Made-with: Cursor
* Popover: use VisuallyHidden render prop pattern in stories
Replace `<Popover.Title><VisuallyHidden>text</VisuallyHidden></Popover.Title>`
with `<VisuallyHidden render={<Popover.Title />}>text</VisuallyHidden>` across
all stories. This preserves the `<h2>` semantics and ARIA wiring from
`_Popover.Title` while applying visually-hidden styles directly to the heading,
avoiding extra wrapper elements.
Made-with: Cursor
* Popover: remove inline prop, document as consumer pattern
Remove the built-in `inline` prop from `Popover.Popup`. Consumers who
need inline (non-portaled) rendering can achieve the same result by
creating a local ref to a `<span style="display: contents">` and passing
it as the `container` prop — this is now documented in the Inline story.
This simplifies the component by removing a special code path and
keeps the API surface small, since `container` already covers the
use case.
Made-with: Cursor
---
Co-authored-by: ciampo <mciampini@git.wordpress.org>
Co-authored-by: aduth <aduth@git.wordpress.org>
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: jameskoster <jameskoster@git.wordpress.org>
What?
Change
Dialog.Popup's defaultinitialFocusbehavior to deprioritize the close icon button, focusing the first tabbable content element instead.Why?
The WAI-ARIA APG notes that "the most appropriate focus placement will depend on the nature and size of the content." Deprioritizing the close icon for initial focus is a widely adopted UX best practice (e.g., Radix, Reach UI) — focusing it by default suggests dismissal is the primary action before the user has interacted with the content.
How?
useDeprioritizedInitialFocushook (packages/ui/src/utils/) that wraps theinitialFocusprop with close-icon-aware logic. Designed for reuse byPopover.Popupin@wordpress/ui: addPopover#76438.Dialog.Popupconsumes the hook;Dialog.CloseIconis identified via adata-wp-ui-dialog-close-iconattribute.tabbableas a direct dependency (already a transitive dep via@base-ui/react).Hook behavior
When
initialFocusisundefinedortrue:tabbablelibrary (same as Base UI), returns the first one without the close-icon attribute. Falls back to Base UI's default when the close icon is the only tabbable element.All other
initialFocusvalues (false,RefObject, callback) pass through unchanged.Testing Instructions
Dialogstorybook example.initialFocus={false}still prevents focus movement.initialFocuscallback still works as expected.Use of AI Tools
Cursor + Claude Opus 4.6