Skip to content

Theme: apply ThemeProvider styles inline (I2)#78678

Draft
ciampo wants to merge 2 commits into
trunkfrom
ciampo/theme-unify-inline-style-propagation
Draft

Theme: apply ThemeProvider styles inline (I2)#78678
ciampo wants to merge 2 commits into
trunkfrom
ciampo/theme-unify-inline-style-propagation

Conversation

@ciampo

@ciampo ciampo commented May 26, 2026

Copy link
Copy Markdown
Contributor

What?

Addresses I2 from #77462. Replaces ThemeProvider's per-instance <style> with inline custom properties on the wrapper, mirrored onto document.documentElement when isRoot.

Why?

One fewer DOM node per provider and a simpler mental model — no per-instance scoping selector to build, no doubled-class specificity hack.

How?

  • Color/cursor tokens go on the wrapper's style prop.
  • For isRoot, a useLayoutEffect mirrors them onto document.documentElement.style and restores on cleanup.
  • Drops the per-instance <style>, useId, the doubled-class hack, the runtime :root:has(…) selector, and data-wpds-theme-provider-id.
  • Keeps data-wpds-root-provider="true" on the wrapper so prebuilt CSS can forward preset-based settings to :root (see Theme: forward ThemeProvider cornerRadius preset to :root for root providers #79153).
Why the asymmetry between color/cursor and cornerRadius forwarding?

Dynamic values (color seeds, cursor) are per-instance and can't be expressed in a static stylesheet, so JS syncs them to <html>. Preset-based values (e.g. cornerRadius) are a small known set fully resolved in the prebuilt CSS, where :root:has([data-wpds-root-provider="true"][data-wpds-corner-radius="…"]) is the cleanest forwarding mechanism.

Testing Instructions

  1. Render <ThemeProvider isRoot color={…} cursor={…} cornerRadius="…"> with a few nested <ThemeProvider>s.
  2. In DevTools: confirm no <style> element under the provider, color/cursor tokens are inline on the wrapper, and the root wrapper has data-wpds-root-provider="true".
  3. Confirm root color/cursor reach <html> and revert on unmount.
  4. Confirm portals (popovers, drawers, dialogs) still inherit the root provider's tokens.
  5. npm run test:unit -- packages/theme passes.

Testing Instructions for Keyboard

No UI-facing changes.

Screenshots or screencast

N/A — no visual changes intended.

Use of AI Tools

Made with Cursor.

@github-actions github-actions Bot added [Package] Theme /packages/theme [Package] UI /packages/ui labels May 26, 2026
@ciampo ciampo changed the title Theme: unify color/cursor/density propagation via inline styles Theme: apply ThemeProvider styles inline (I2) May 26, 2026
@github-actions

github-actions Bot commented May 26, 2026

Copy link
Copy Markdown

Size Change: -30 B (0%)

Total Size: 8.58 MB

📦 View Changed
Filename Size Change
build/scripts/theme/index.min.js 22.2 kB -30 B (-0.14%)

compressed-size-action

@github-actions

Copy link
Copy Markdown

Flaky tests detected in fed1832.
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/26456497499
📝 Reported issues:

Comment thread packages/ui/src/drawer/style.module.css
@ciampo ciampo self-assigned this May 26, 2026
ciampo added a commit that referenced this pull request May 27, 2026
In anticipation of #78678, which replaces ThemeProvider's per-instance
`<style>` element with inline custom properties on the wrapper, reword
the comments / CHANGELOG / README / PR description to describe the
optimization as "skip applying per-instance overrides" rather than
"skip emitting the `<style>` element". The savings are real in either
mechanism (no CSS string assembly + no DOM write).

Also simplify the `AcrossIframes` story:
- The previous filter cloned `<style data-wpds-theme-provider-id>`
  elements from `document.head`, which is dead code (React-rendered
  `<style>` is not in head) and would be doubly dead post-#78678
  (the attribute is removed entirely). Drop it.
- Per-instance overrides ride into the iframe with the portalled
  `<ThemeProvider>` itself (whether the mechanism is a `<style>`
  child or an inline `style` prop on the wrapper). The story only
  needs to clone the prebuilt token stylesheet `<link>`.
ciampo added a commit that referenced this pull request May 27, 2026
In anticipation of #78678, which replaces ThemeProvider's per-instance
`<style>` element with inline custom properties on the wrapper, reword
the comments / CHANGELOG / README / PR description to describe the
optimization as "skip applying per-instance overrides" rather than
"skip emitting the `<style>` element". The savings are real in either
mechanism (no CSS string assembly + no DOM write).

Also simplify the `AcrossIframes` story:
- The previous filter cloned `<style data-wpds-theme-provider-id>`
  elements from `document.head`, which is dead code (React-rendered
  `<style>` is not in head) and would be doubly dead post-#78678
  (the attribute is removed entirely). Drop it.
- Per-instance overrides ride into the iframe with the portalled
  `<ThemeProvider>` itself (whether the mechanism is a `<style>`
  child or an inline `style` prop on the wrapper). The story only
  needs to clone the prebuilt token stylesheet `<link>`.
Replaces the per-instance `<style>` element with inline `style` on the
wrapper. When `isRoot`, mirrors the same custom properties onto
`document.documentElement` via `useLayoutEffect` so portals and the
`html`/`body` background pick them up.

- Drops `useId`, `data-wpds-theme-provider-id`, `data-wpds-root-provider`,
  and the generated `:root:has(...)` selector.
- Updates `ThemeProvider` tests to scope by the wrapper's CSS module
  class (the previous attribute is gone).
- Fixes the storybook iframe stylesheet filter, which still looked for
  the removed attribute, to match the `--wpds-` token prefix instead.
@ciampo ciampo force-pushed the ciampo/theme-unify-inline-style-propagation branch from 623c53a to 2c668ee Compare June 15, 2026 16:08
@github-actions github-actions Bot removed the [Package] UI /packages/ui label Jun 15, 2026
Restores `data-wpds-root-provider="true"` on the wrapper when `isRoot`,
so prebuilt CSS can forward preset-based settings (e.g. `cornerRadius`
in #79153) to `:root` via `:root:has([data-wpds-root-provider="true"]…)`.

Dynamic color/cursor values keep using the JS sync in `useLayoutEffect`
(they can't be expressed in a static stylesheet). Comment updated to
explain the deliberate split.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Package] Theme /packages/theme

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant