Repository navigation
feat(core): Dialog, Drawer and MobileNav drop the backdrop blur - #8120
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
There was a problem hiding this comment.
The new --color-background-backdrop token, its flat black 20 % / 48 % default and the removal of the modal blur set a design direction that no current spec record covers, so approval waits on an owner providing or identifying the governing spec for it. An engineering owner can record that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner; the decision settles the token and its name, the default, the flat dim in every theme, and Lightbox keeping --color-overlay with its blur. Two fixes are needed whatever the decision:
- Themes that colour the dim through
--color-overlayordefineTheme({color})now fall to the new default, so derive the token inexpandColorScaleand correct the changeset's line that themed apps keep their colour. - In forced colors the blur was the only cue that the page sits behind an open modal, so keep a forced-colors-only blur or border, or have the owner accept that state explicitly.
[Automated review]
| '@astryxdesign/theme-y2k': patch | ||
| --- | ||
|
|
||
| [feat] New `--color-background-backdrop` token: the dim behind a modal. Dialog (and so AlertDialog), Drawer, MobileNav and BottomSheet paint their `::backdrop` with it, and the dim no longer blurs the page behind it. With the base tokens it is the iOS dimming colour: black at 20 % in light mode and 48 % in dark mode, in place of a blue-grey at 40 % (light) or 60 % (dark) with a 2 px blur. Each bundled theme sets the token to the overlay colour it already dimmed with, so a themed app's dim keeps its colour and only loses the blur. `--color-overlay` stays the scrim that keeps text and icons legible over media (Overlay, Thumbnail). `astryx docs tokens/tokens-color`, the Tailwind bridge (`bg-backdrop`) and the theme template list the new token. |
There was a problem hiding this comment.
This holds for the seven bundled themes only. A custom theme that tinted its dim through --color-overlay, or one built with defineTheme({color}) (whose expandColorScale derives --color-overlay but not the new token), now gets black 20 % / 48 %. A warm-neutral color theme resolves --color-overlay to #1A1B2466 but the modal dim to rgba(0, 0, 0, 0.2). Consider deriving --color-background-backdrop in expandColorScale from the same neutral ramp, and tell custom-theme authors to set the new token to keep their dim.
There was a problem hiding this comment.
Covered by the later wording in 8b3cba5 (custom themes on iOS get the black default; how to keep the old look). The token is iOS-only now, so deriving it in expandColorScale would only matter on iOS; the changeset tells custom-theme authors to set it.
| '::backdrop': { | ||
| backgroundColor: colorVars['--color-overlay'], | ||
| backdropFilter: 'blur(2px)', | ||
| backgroundColor: colorVars['--color-background-backdrop'], |
There was a problem hiding this comment.
In forced colors this paints Canvas at the token's alpha, so the dim can only fade the page; the removed blur was the cue that the page sits behind the modal. In Chromium's forced-colors emulation with a light palette, the page behind an open Dialog now stays sharp (rgba(255, 255, 255, 0.2), no filter, against 0.4 with blur(2px) before), and Dialog draws no edge there. A forced-colors-only backdrop-filter or a CanvasText border under @media (forced-colors: active) would keep the cue.
There was a problem hiding this comment.
Since 5e3c43c the flat dim applies only under the iOS gate (nested (pointer: coarse) + (-webkit-touch-callout: none)), and iOS Safari has no forced-colors mode. Desktop and Android in forced colors keep --color-overlay with its 2px blur, which the spec's forced-colors case asserts.
PR Analysis Report🔎 Preview ·
|
| Component | Size | LoC | Visual · A11y · RTL |
|---|---|---|---|
| MobileNavToggle | 45.8 kB (0) · 9.7 kB CSS (0) | — | ✓ · ✓ · ✗ |
| Dialog | 16.9 kB (+23 B) · 6.7 kB CSS (+52 B) | — | ✓ · ✓ · — |
| CommandPalette | 48.9 kB (+23 B) · 12.2 kB CSS (+43 B) | — | — · — · — |
15 more components
| Component | Size | LoC | Visual · A11y · RTL |
|---|---|---|---|
| richtext/RichTextEditorToolbar | 222.3 kB (+27 B) · 454 B CSS (0) | — | — · — · — |
| lab/CommentThread | 227.3 kB (+22 B) · 2.4 kB CSS (0) | — | — · — · — |
| lab/MobileTokenizer | 101.2 kB (+21 B) · 880 B CSS (0) | — | — · — · — |
| lab/Comments | 232.6 kB (+16 B) · 2.5 kB CSS (0) | — | — · — · — |
| lab/MobilePowerSearch | 214.0 kB (+9 B) · 1.2 kB CSS (0) | — | — · — · — |
| DialogHeader | 48.4 kB (0) · 10.2 kB CSS (0) | — | ✓ · ✓ · ✓ |
| DrawerHeader | 48.5 kB (0) · 11.6 kB CSS (0) | — | — · ✓ · ✓ |
| AlertDialog | 56.5 kB (+18 B) · 12.7 kB CSS (−25 B) | — | — · — · — |
| PowerSearch | 184.6 kB (+1 B) · 20.6 kB CSS (−25 B) | — | — · — · — |
| Tokenizer | 70.3 kB (0) · 17.1 kB CSS (−24 B) | — | — · — · — |
| TopNav | 51.8 kB (−12 B) · 10.7 kB CSS (−22 B) | — | — · — · — |
| MobileNav | 50.2 kB (−12 B) · 10.4 kB CSS (−23 B) | — | ✓ · ✓ · ✓ |
| SideNav | 66.0 kB (−13 B) · 10.9 kB CSS (−22 B) | — | — · — · — |
| AppShell | 68.5 kB (−14 B) · 12.2 kB CSS (−23 B) | — | — · — · — |
| Drawer | 36.9 kB (−16 B) · 8.2 kB CSS (−23 B) | — | — · ✓ · ✓ |
Package size: @astryxdesign/core 6.09 MB packed (+255 B); no new dependencies.
Touched components checked; add ci:run-visual, ci:run-a11y or ci:run-rtl to run the full suites.
Generated by PR Enrichment workflow | View full report
There was a problem hiding this comment.
The iOS modal dim adds direction no current spec sets (the new --color-background-backdrop token, its black 20 % / 48 % default, the dropped blur under the iOS gate, and the INV10 backdrop and glass surface roles), so approval waits on an owner providing or identifying the governing spec. An engineering owner can record that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. The decision covers the token and its iOS-only scope, the platform gate, the default and blur, and the edges: custom themes on iOS fall to the default, and Lightbox keeps the blurred overlay. Whatever the decision, these need fixing:
- Rebase onto
mainand runpnpm -F @astryxdesign/native generate:swift-ios: the committed Swift theme files lack the new token, so the native drift test fails. - Correct the changeset: themes that tint the dim through
--color-overlayordefineTheme({color})get the black default on iOS, and it should say how to keep the old look. - Add iOS Safari captures of Drawer, MobileNav and BottomSheet, since the recordings show Dialog only.
[Automated review]
| '@astryxdesign/theme-y2k': patch | ||
| --- | ||
|
|
||
| [feat] New `--color-background-backdrop` token: the dim behind a modal on iOS. Under a finger on WebKit for iOS, Dialog (and so AlertDialog), Drawer, MobileNav and BottomSheet paint their `::backdrop` with it, and the dim no longer blurs the page behind it. With the base tokens it is the iOS dimming colour: black at 20 % in light mode and 48 % in dark mode, in place of a blue-grey at 40 % (light) or 60 % (dark) with a 2 px blur. Each bundled theme sets the token to the overlay colour it already dimmed with, so a themed app's dim keeps its colour on iOS and only loses the blur. Everywhere else (a mouse, Android) the modals keep `--color-overlay` and its blur, unchanged. `--color-overlay` also stays the scrim that keeps text and icons legible over media (Overlay, Thumbnail). `astryx docs tokens/tokens-color`, the Tailwind bridge (`bg-backdrop`) and the theme template list the new token. |
There was a problem hiding this comment.
This holds for the seven bundled themes only. On iOS, a theme that tints its dim through --color-overlay, or one built with defineTheme({color}) (expandColorScale derives --color-overlay but not the new token), gets black 20 % / 48 %: a warm-neutral color theme resolves --color-overlay to #1A1B2466 but the iOS dim to rgba(0, 0, 0, 0.2). Please say so, and add how to keep the old look (set --color-background-backdrop, plus a ::backdrop backdropFilter override for the blur), which visual-direction changesets name.
There was a problem hiding this comment.
Fixed in 8b3cba5: the changeset now says a custom theme that tints its dim through --color-overlay or defineTheme({color}) gets the black default on iOS, and how to keep the old look (set --color-background-backdrop, plus a ::backdrop backdropFilter override).
| - name: Run modal close visibility guard | ||
| run: node .github/scripts/modal-close-visibility.js --storybook-dir apps/storybook/dist | ||
|
|
||
| # Every modal surface dims the page with one theme colour, |
There was a problem hiding this comment.
This describes the earlier design. At this head only the iOS gate paints --color-background-backdrop without blur; elsewhere the modals keep --color-overlay with blur(2px), which this spec's Android and mouse cases assert, and Lightbox keeps the overlay everywhere.
There was a problem hiding this comment.
Fixed in 8b3cba5: the step comment now describes the iOS gate (iOS: the token, unblurred; a mouse or an Android finger: --color-overlay with its 2px blur; Lightbox: the overlay everywhere).
|
Posted by Cindy's agent on her behalf. Parts of this PR re-implement behavior that already exists, or is being specified, as a shared part of core. Converging keeps one implementation per behavior:
This is about where the behavior lives, not about the PR's intent. |
There was a problem hiding this comment.
The iOS modal dim adds direction no current spec sets (the new --color-background-backdrop token, its black 20 % / 48 % default, the dropped blur under the iOS gate, and the INV10 backdrop and glass surface roles), so approval waits on an owner providing or identifying the governing spec for it. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner; the decision covers the token and its iOS-only scope, the platform gate, the default and the blur, and the edges: custom themes on iOS get the black default unless they set the token, and Lightbox keeps the blurred overlay. The earlier fixes are all in (regenerated Swift theme files, the changeset's custom-theme and keep-the-old-look wording, iOS recordings of Drawer, MobileNav and BottomSheet, the CI comment), so nothing else in this review stands in the way once the direction is decided.
[Automated review]
|
Agreed: one implementation.
|
There was a problem hiding this comment.
Approval recommended: removing the modal backdrop blur matches the engineering owner's decision to drop it under every pointer, and Dialog, AlertDialog, CommandPalette, the scrim Drawer and MobileNav all render unblurred under the unchanged dim, with Dialog's 1px edge showing in forced colors. No changes are required. Optionally, the changeset could also name CommandPalette, which renders through Dialog and changes the same way, and Lightbox, which still blurs its backdrop, could follow in a separate change if the owner wants "everywhere" to cover it.
[Automated review]
There was a problem hiding this comment.
Approval waits on an owner decision: no current spec record states the dim-only, every-pointer modal backdrop or Dialog's forced-colors edge, and the owner decision on this pull request asked for the record to match, which this head does not do. An engineering owner (the author included) or a design owner can answer here: either the decision stands without a record, or name the record this pull request should add (for example a Design decisions row in the Dialog, Drawer and MobileNav records) and say whether it covers Lightbox.
Required either way:
- Retitle the pull request and its commit to name the changed components, because Lightbox is a modal that still blurs its backdrop (
packages/core/src/Lightbox/Lightbox.tsx:148-151), so "modals drop the backdrop blur" overstates the change.
The inline notes on three backdrop comments are optional suggestions.
[Automated review]
| // Backdrop using ::backdrop pseudo-element. It does not blur the page: a | ||
| // full-viewport backdrop-filter is costly to repaint. The dim is the | ||
| // theme's overlay colour. |
There was a problem hiding this comment.
Optional (comment style): "Backdrop using ::backdrop pseudo-element" and "The dim is the theme's overlay colour." restate the code below. Shorter, keeping only the reason:
// No backdrop-filter: a full-viewport blur is costly to repaint.| // The page behind is not blurred: a full-viewport backdrop-filter is | ||
| // costly to repaint. The dim is the theme's overlay colour. |
There was a problem hiding this comment.
Optional (comment style): "The dim is the theme's overlay colour." restates the next line. Shorter:
// No backdrop-filter: a full-viewport blur is costly to repaint.| // The page behind is not blurred: a full-viewport backdrop-filter is | ||
| // costly to repaint. The dim is the theme's overlay colour. |
There was a problem hiding this comment.
Optional (comment style): "The dim is the theme's overlay colour." restates the next line. Shorter:
// No backdrop-filter: a full-viewport blur is costly to repaint.|
Per the owner's decision on this pull request, b76bcae adds the backdrop to the records: |
There was a problem hiding this comment.
Approval waits on an owner decision: the backdrop record text this pull request adds (component:Dialog FR9, the Drawer and MobileNav backdrop sentences with their verification rows, and AlertDialog's inheritance note) is new in current spec records and has no spec-owner approval yet. An engineering owner (the author may, as one) or a design owner records it by commenting /approve-spec <full head SHA>, or says what to change; that decision also settles whether Lightbox, which keeps its blur, stays out of scope. Because the fix below edits FR9, the approval should name the head that carries it.
Required either way:
component:DialogFR9 and its verification row require a forced-colors edge from every Dialog, but a full-screen Dialog draws none (packages/core/src/Dialog/Dialog.tsx:262-263), so add that exception to FR9 and to the row.
Optional: the basis line "the blur is dropped on every device" describes the change rather than the system; stating the result, for example "unblurred on every pointer", keeps it a record sentence.
The blur removal itself checks out in Chromium with a mouse, a finger and forced colors, and the new backdrop test fails on the old components and passes here.
[Automated review]
| | FR6 | Initial-focus selection MUST follow the rendered eligible descendants, not a private component type or source-only assumption. | composable descendant contract | settled | | ||
| | FR7 | A native modal Dialog MUST be a focus region through Core's shared focus-region module: Tab from its last tabbable control moves focus to its first, and Shift+Tab from its first moves focus to its last, so focus never leaves the modal for the browser's own interface. When a Dialog opens from a Dialog, the innermost one holding focus answers Tab. | owner decision DEC-2 | settled | | ||
| | FR8 | A modal Dialog open on the server renders closed until hydration (no open styles, no `aria-modal`, the markup of a closed Dialog) and opens in the render after hydration, the first that can make it modal; it never claims a modality it does not have (`spec:AST-020` FR1, WCAG 2.2 SC 4.1.2). An inline Dialog is not modal and renders as it is. The server and hydrating renders produce the same markup (`spec:AST-013` FR9), and the open after hydration is settled, with no entry motion; later opens and closes animate as usual (`spec:AST-013` FR13). | `spec:AST-020` FR1 | settled | | ||
| | FR9 | On every pointer, the modal backdrop is the theme's `--color-overlay` dim with no blur (no `backdrop-filter`); under forced colors the dialog keeps a visible edge (a system-colour border) so it stays apart from the page. | Owner decision, 2026-10-25: the overlay stays theme-controlled and the blur is dropped on every device. | settled | |
There was a problem hiding this comment.
This asks every Dialog for a visible edge in forced colors, but the full-screen style resets the border to 0 (packages/core/src/Dialog/Dialog.tsx:262-263), so variant="fullscreen" draws none. Suggest adding "a full-screen Dialog (variant="fullscreen") covers the page and draws none" here and to the FR9 verification row's states.
There was a problem hiding this comment.
Added in d4eb638: FR9 and its verification row now say a full-screen Dialog covers the page and draws none, with a test for it. The basis line now states the result ("unblurred on every pointer"), as you suggested.
Dialog (and so AlertDialog and CommandPalette), Drawer and MobileNav no longer blur the page behind the modal, on every device and pointer, desktop included: repainting a full-viewport 2px `backdrop-filter` is costly (on the Android emulator the blurred dim draws at about 20 fps while it opens, 60 without it). The dim keeps the theme's `--color-overlay`, and in forced colors each modal keeps a 1px edge so it still stands apart from the page. BottomSheet never blurred. A theme keeps the blur by setting `backdropFilter` on the component's modal backdrop. `ModalBackdrop.a11y.browser.spec.ts` proves it in Chromium and WebKit, under a finger and with a mouse, and in forced colors; its `@pr-a11y scope` line runs it when a pull request changes it, one of the modals or their stories.
There was a problem hiding this comment.
Approval waits on one owner decision: the backdrop rule this pull request adds to the component records (component:Dialog FR9, the Drawer and MobileNav backdrop text with their verification rows, and AlertDialog's inheritance note) is new record text that no owner has approved at this head.
The code matches that text, including the full-screen exception now in FR9, and the new backdrop test fails on the old components and passes here.
To finish, an engineering owner (or a design owner, for the visual direction) records the decision by commenting /approve-spec <full head SHA>, which the author may do as an engineering owner. Approving also accepts that the rule sits in a contract row and two impact bullets rather than one shared design rule, and that Lightbox keeps its blur.
[Automated review]
|
/approve-spec bd62070 |
There was a problem hiding this comment.
Approval recommended. Dialog (and so AlertDialog and CommandPalette), the scrim Drawer and MobileNav no longer blur the page behind the modal, at the same theme dim, and Dialog draws a 1px edge in forced colors (none when full-screen). An engineering owner approved the new component:Dialog FR9, component:Drawer FR20 and component:MobileNav FR13 rows at this head, and the styles match them: in Chromium the backdrop computes blur(2px) before and none after under a mouse and a finger, and the new backdrop test fails on the previous styles and passes here. No changes are needed.
[Automated review]
User impact
Dialog (and so AlertDialog and CommandPalette), Drawer and MobileNav no longer blur the whole page behind the modal, under every pointer. Repainting a full-viewport 2 px
backdrop-filteris costly: on the Android emulator, main's blurred dim draws at about 20 fps while it opens, this branch's at 60. The dim keeps the theme's--color-overlay; nothing else changes.Visual authority
DDrow reused or amended.Design decisionstable by a team design owner.docs/design/.None of these routes applies. The owner decided on this pull request: the overlay stays theme-controlled, with no new token and no colour change; a forced-colors edge keeps the modal apart from the page; and the blur is dropped on every pointer. At the owner's request, the records state it:
component:DialogFR9,component:DrawerFR20 andcomponent:MobileNavFR13, one verification row each, and AlertDialog's inheritance note. Lightbox keeps its blur.Before and after
Before (top) and after (bottom), iOS 26.2 Safari, base tokens, the page behind an opening Dialog: the same dim colour; the text behind is no longer blurred.
With a mouse, Chromium desktop, light and dark: before, then after.
After, Android 15, Chrome 124 (emulator), light and dark: 16.7 ms frames while the Dialog opens.
State matrix
backdropFilter: 'blur(2px)'on the modal backdrop:components.dialog.base['::backdrop'],components['mobile-nav'].base['::backdrop'], andcomponents.drawer.base[':modal::backdrop'](a non-modal drawer keeps none). Theme rules sit in a layer above the components' own.Scope
Testing
Local gates at
bd62070d68(this head, one commit on main1821fb3fae). Runs marked with an earlier SHA used this change on main449486fdce.pnpm check:knowledge,pnpm check:changesetsand typecheck pass.modalBackdrop.test.tsxand the Dialog, AlertDialog, Drawer, MobileNav, CommandPalette and BottomSheet unit tests: 43 files, 969 tests pass. The tests that read the records: 4 files, 294 pass. The new full-screen case fails when the full-screen style keeps its border.ModalBackdrop.a11y.browser.spec.tspasses 29 cases with 1 evidence-only skip in Chromium, and 21 with 9 skips in WebKit under the CI WebKit job's command (forced colors is Chromium-only). Finger cases run at 390 × 844 and mouse cases at 1280 × 800. The touched components' accessibility contracts (release-only included) pass, 26 files and 250 tests.pnpm buildpasses, and the package'sastryx.csshas nobackdrop-filterfor Dialog, Drawer or MobileNav (its fixed blurs are Lightbox's2pxand ChatLayout's dock).pnpm lintand the strict comment lint report 0 errors, andpnpm test:uipasses 755 files and 19,025 tests.pnpm test:node --changedfinds no affected node test.b76bcaed53on main449486fdce, which differs only in record text and one unit test case: inpnpm test:node, 857 files pass and 4 CLI tests time out at 30 s under load, in files this pull request does not touch. The same 4 files pass atb76bcaed53and on main at 4 workers (87 tests each). That is a known load-sensitive case on main, with its cause fix in test(cli): doctor-integration, validate-integration and check-failures fixtures check their own files #9496, perf(cli): doc types and props checks reuse parsed files (TS 5.3+) #9517 and perf(cli): discovery reads its listings; CLI tests warm up once #9722.b76bcaed53on main449486fdce: against a Storybook build of main, the spec fails 20 of its 30 cases, every blur case of Dialog, AlertDialog, Drawer and MobileNav (under a finger, in the neutral theme and with a mouse) and Dialog's and AlertDialog's forced-colors edge. The 9 that pass are ones main already meets (BottomSheet never blurred, Drawer's and MobileNav's side border, the media scrim); 1 is the evidence-only skip.The iOS 26.2 simulator and Android 15 emulator recordings above, the Chromium desktop before and after, and the theme-override check were made at an earlier version of this pull request with the same product change. Read page-side on Android: this branch rgba(1, 18, 40, 0.4) / rgba(17, 17, 18, 0.6) dark with no filter; main the same colours with blur(2px). Main's blurred open steps at about 50 ms per frame there, this branch's at 16.7 ms. The theme override: a theme-layer rule setting
backdrop-filter: blur(2px)on.astryx-dialog::backdrop,.astryx-drawer:modal::backdropand.astryx-mobile-nav::backdropcomputesblur(2px)for each; the non-modal Drawer keepsnone.Writing rubric results
#numbers,/pull/or/issues/in the changed lines).