Repository navigation
ui: Restore overlay borders and tokenize backdrop on Dialog/AlertDialog/Drawer - #77746
jameskoster wants to merge 6 commits into
Conversation
…og/Drawer Follow-up to #77691. * Always show the token-colored hairline border on Dialog, AlertDialog, and Drawer (including modal mode); drop the `.backdrop ~ * .popup { border-color: transparent }` override and simplify the forced-colors block to a single `.popup` selector. * Replace the hardcoded `rgba(0, 0, 0, …)` / `rgb(0, 0, 0)` backdrop colors with `--wpds-color-bg-surface-neutral-weak`, and bump the backdrop opacity from 35% to 75% so the lighter token retains comparable presence. Dialog now drives the alpha via the `opacity` property (matching Drawer's existing pattern), removing the now-redundant `&[data-open] { opacity: 1 }` rule. AlertDialog inherits both changes via composition from Dialog's CSS module. * Inset Drawer's popup by 4px from every anchored viewport edge (`--popup-inset: var(--wpds-dimension-padding-xs)`) and round all four corners, so the always-on hairline border no longer butts directly against the viewport edge. Per-direction `border-*-radius` declarations are removed in favor of a single `border-radius` on `.popup`. Swipe-out transforms are extended by the inset so the popup still slides fully off-screen. Made-with: Cursor
|
Size Change: +254 B (0%) Total Size: 7.82 MB 📦 View Changed
ℹ️ View Unchanged
|
|
Flaky tests detected in 65b5b7d. 🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/25114649244
|
|
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. |
…orced-colors The popup already has a colored token border, so the UA substitutes a system color (typically CanvasText) automatically in forced-colors mode. The explicit override is unnecessary. Made-with: Cursor
Mirror the Dialog/Drawer change: rely on UA color substitution for the popup border in forced-colors mode. The backdrop variant keeps its transparent border (the backdrop provides visual containment). Made-with: Cursor
Mirror the prior Dialog/AlertDialog/Drawer change on Popover:
* Always show the token-colored hairline border on Popover (including
when a backdrop is rendered); drop the
`.backdrop ~ * .popup { border-color: transparent }` override.
* Replace the hardcoded `rgba(0, 0, 0, 0.15)` backdrop color with
`--wpds-color-bg-surface-neutral-weak` and bump the opacity to 75%
so the lighter token retains comparable presence, matching the
Dialog/Drawer backdrop styling.
Made-with: Cursor
Mirror the recent overlay backdrop change on the legacy `Modal`: * Replace the hardcoded `rgba($black, 0.35)` scrim with `--wpds-color-bg-surface-neutral-weak` and bump the alpha to 75% so the lighter token retains comparable presence, matching the Dialog/Drawer/Popover backdrops. * Bake the alpha into the color via `color-mix` rather than the `opacity` property, so the existing fade-in keyframe animation still ramps from 0 → fully opaque token color (animation-fill-mode would otherwise override a static `opacity: 0.75`). The `Modal` frame has no popup border to begin with, so the "always-on hairline border" half of the prior overlay change does not apply here. Made-with: Cursor
Match the always-on token-colored hairline border now present on the Dialog/AlertDialog/Drawer/Popover overlays, so the popup edge reads as a defined boundary against the lighter tokenized backdrop instead of relying on the elevation shadow alone. `@include reset` already applies `box-sizing: border-box`, so the 1px border is absorbed into the existing frame dimensions and does not shift layout. Made-with: Cursor
|
Apart from a missing CHANGELOG entry for the At this point, it's a design decision. @jameskoster , do you think we should ship it, or gather more feedback first? |
|
Let's wait for feedback from @WordPress/gutenberg-design. |
|
@jameskoster we could also extract the inset positioning changes to a separate PR and land it immediately, in case that's not something to debate? Partially related, I've also noticed some exinsting bugs in |
|
The inset positioning is a dependency of adding the visible stroke. Otherwise the stroke creates visual tension with the viewport boundary. |
|
Thanks for the ping. I don't personally love the border on a purely visual level; it adds a little bit of visual noise in an otherwise very clean context. I'm also not quite sure I understand precisely why we need a border: it doesn't meet any contrast ratios and thus doesn't seem to technically improve anything that isn't already handled, if more subtly, by the drop shadow. It looks to me purely as a style change, one that I'm not completely convinced about. However that's purely a personal opinion, and I don't feel strongly about it. I don't want to block any of this work from moving forward, so consider that first paragraph mostly as a question. I will also say if we do move forward, I think we should lean into it as a style change: seek out where this shares visual language with other elements in the component library and emphasise that. E.g. does this share some visual cues from the Card component? If yes, perhaps that means the borders should be the same? Conceptually we could think of this as simply an "elevated card" (that happens to have modal behavior). A final idea which would almost certainly be outside the bounds of this effort: I've seen some modals focus the modal itself on mount, rather than the first tabbable (which almost inevitably means the close button). In those cases, the focus style is on the card itself. If we were to do this, it might translate cleanly to our other focusables: 1px border at rest, 1.5px theme color on focus. It might still have focus-visible handling, but it would be nice when keyboarding through. Is that helpful? Let me know any nuance I'm missing. |
|
The main motivation is visual alignment. As noted, I don't think the change is a requirement though. We render a border on
So if we're not feeling the design let's close. What do you think @ciampo? |
|
Sure, we can close this PR and keep it mind for future explorations.
|




What
Follow-up to #77691. Three related visual tweaks to the overlay popups:
Dialog,AlertDialog,Drawer,Popover, andModal(@wordpress/components) — including when a backdrop is rendered. Previously ui: Unify hairline border across overlay popups #77691 hid the border in modal mode because the popup edge looked fuzzy against the dark backdrop.Modalhad no border at all; one is added to match.rgba(0, 0, 0, …)/rgb(0, 0, 0)backgrounds with--wpds-color-bg-surface-neutral-weak, and bump opacity from 35% (or 15% onPopover) → 75% so the lighter token retains comparable presence. Applied acrossDialog,Drawer,Popover, andModal.Drawer's popup by 4px from every anchored viewport edge and round all four corners, so the now-always-on hairline border doesn't butt directly against the viewport edge.AlertDialoginherits the first two changes via composition (itsPopupalready usesdialogStyles.backdrop/dialogStyles.popup), so it picks them up without a code change.Why
In #77691 we landed an a11y fix that gave every overlay a visible border in forced-colors mode. To accommodate, we suppressed the regular-mode border whenever a backdrop was rendered, because the popup edge looked clipped/fuzzy against the dark scrim (#77691 (comment)). The cause was contrast: a near-black scrim made the token-colored stroke read as a thin cut.
Switching the backdrop to a neutral surface token instead of pure black removes the contrast issue at its source, lets the popup keep its border in every mode, and brings overlay scrim color into the design system. A small 4px inset on
Drawerdoes the same thing for the viewport-edge case (where the border was being clipped by the screen edge instead of by the scrim). Extending the same border + tokenized scrim toPopover's backdrop variant and to the legacyModalkeeps the full overlay family visually consistent.Behavior
CanvasTextborder (UA-substituted)CanvasTextborder (UA-substituted)CanvasTextborder (UA-substituted)CanvasTextborder (UA-substituted)CanvasTextborder (UA-substituted)CanvasTextborder (UA-substituted)backdropprop)CanvasTextborder (UA-substituted)@wordpress/components)CanvasTextborder (UA-substituted)SelectandTooltipare untouched — they were already correct after #77691.Implementation notes
Dialog's backdrop now drives its alpha through theopacityproperty (matchingDrawer's existing pattern). The previous&[data-open] { opacity: 1 }rule becomes redundant — Base UI only setsdata-starting-style/data-ending-styleduring transitions, so the resting state is the defaultopacity: 0.75, and the existingtransition: opacityinterpolates 0 ↔ 0.75..backdrop ~ * .popup { border-color: transparent }rule is removed inDialog,Drawer, andPopover.@media (forced-colors: active) { .popup { border-color: CanvasText } }blocks are removed entirely fromDialog,Drawer, andPopover. The popup already declares a colored token border, so the UA substitutes a system color (typicallyCanvasText) automatically in forced-colors mode — same pattern documented inoverlay-chrome.module.cssfor the scroll separator. This removes the last special-case block from the popup CSS.Drawerintroduces--popup-inset: var(--wpds-dimension-padding-xs)(4px), applied to every anchored side per direction. Per-directionborder-*-radiusdeclarations are removed in favor of a singleborder-radius: var(--wpds-border-radius-lg)on.popup. Swipe-out transforms are extended totranslate(±100% ± var(--popup-inset))so the popup still slides fully off-screen from its inset resting position.max-width/max-heightcalcs onDrawerremain100vw - var(--viewport-inset)— they're a generous safety bound and the trailing-edge gap stays comfortably within the viewport even with the new 4px inset.Modal(@wordpress/components) bakes the 75% alpha into the backdrop color viacolor-mix(in srgb, var(--wpds-color-bg-surface-neutral-weak), transparent 25%)rather than theopacityproperty, because the existinganimation__fade-inmixin usesanimation-fill-mode: forwardsand would override a staticopacity: 0.75. The new border on.components-modal__framematchesDialog's declaration (var(--wpds-border-width-xs) solid var(--wpds-color-stroke-surface-neutral));@include resetalready appliesbox-sizing: border-box, so the 1px is absorbed into existing dimensions and does not shift layout.Testing instructions
npm run start, then open the Storybook forDialog,AlertDialog,Drawer,Popover, andModal.Dialogdefault,AlertDialog,Drawerdefault,Popover"Backdrop" story,Modaldefault). The backdrop should now read as a soft light wash rather than a near-black scrim, and the popup should have a visible 1px hairline border around all four edges.Dialog/Drawerstories and the default (no-backdrop)Popoverstory. These were already showing the border in ui: Unify hairline border across overlay popups #77691 — confirm no regression (still token border, no backdrop).Drawerstories for each of the four directions (left/right/up/down). The popup should sit ~4px off the anchored viewport edge with all four corners rounded, and there should be a continuous border around the perimeter (no clipped/cut-off edge against the viewport).Drawerfrom each direction. The popup should still slide fully off-screen with no sliver remaining at the anchored edge.CanvasTextborder around its edge — now via UA color substitution rather than an explicit forced-colors override.prefers-reduced-motionand confirm the backdrop snaps in/out without the opacity transition (Dialog/Drawer/Popover);Modal's fade-in mixin is already gated onnot (prefers-reduced-motion), so its backdrop should also snap in.Screenshots
trunk)