Global Styles: explore a cascade popover for inherited-value overrides - #80993
Draft
JosVelasco wants to merge 1 commit into
Draft
Global Styles: explore a cascade popover for inherited-value overrides#80993JosVelasco wants to merge 1 commit into
JosVelasco wants to merge 1 commit into
Conversation
Exploration, not proposed for merge as-is. Builds on the override-indicator work in WordPress#80649 and stays behind the `gutenberg-global-styles-inheritance-ui` experiment added in WordPress#80815. Where WordPress#80649 marks an override with a diamond and an "Overrides inherited styles" tooltip, this makes the indicator a way in: activating it opens a popover showing the property's whole cascade — every layer that contributed, the losing ones struck through — with a Clear action beside the value it would restore. Engine: `resolveStyle` retains the full per-path cascade rather than only the winning layer. The ordered `contributions` array was already built and then collapsed during the merge; this records what it discarded. `sources` is unchanged, so nothing downstream is affected. Layers carry structural metadata (block name, element, variation) instead of a pre-built string, so the UI composes and translates the labels. UI: origin labels describe scope rather than structure — "Site-wide", "All Paragraph blocks", "Subtitle style", "Just this block", "Custom CSS". Two things the cascade alone could not explain: - A block's own custom CSS overrides a property without touching its style path, so it appears as its own cascade layer rather than as an unexplained discrepancy. - Everything outside the Global Styles data model (theme stylesheets, plugin styles, Customizer CSS) is invisible to the engine. The popover compares the winning value against the element's computed style and says so when they disagree, rather than asserting a winner the page contradicts. Also drops the at-rest dotted-underline treatment, matching the "nothing at rest, something on override" direction settled in WordPress#80649, and adopts the review feedback there: a larger diamond (the 6px square read as a dot) and accessible names that identify the property.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Important note
This is an exploration, opened as a draft to keep the conversation going. It is not proposed for merge as-is. It builds directly on the discussion in #80649 and is meant as something concrete to react to — take it over, cherry-pick from it, or close it.
What?
Follow-up exploration to #80649. That PR settles on nothing at rest, a diamond on override, with the diamond as a status indicator that performs no action and shows "Overrides inherited styles" on hover.
This keeps that model and asks what happens if the indicator becomes a way in rather than a dead end. Activating it opens a popover showing the property's whole cascade — every layer that contributed, the losing ones struck through — with a Clear action beside the value it would restore.
Everything stays behind the
gutenberg-global-styles-inheritance-uiexperiment added in #80815.styles.mp4
Why?
Two threads from #80649 pointed here.
@mirka, on why the infotip pattern was chosen over a plain tooltip:
@t-hamano, on a problem with the status-only indicator:
with two options offered: add a reset action to the indicator, or restore the reset button beside it. This explores the first, which he noted "deviates from the original purpose of this PR" — hence a separate draft rather than a push to that branch. Note @jasmussen preferred restoring the button, so this takes a side in a live disagreement.
Beyond that, a one-bit marker can say overridden but not what was overridden, what the value would fall back to, or why the control disagrees with the canvas. Those are the questions that send people to the browser inspector.
How?
Engine.
resolveStyleretains the full per-path cascade rather than only the winning layer. The orderedcontributionsarray was already built and then collapsed during the merge; this records what it discarded, ascascade: Record<path, CascadeEntry[]>.sourcesis unchanged, so nothing downstream is affected. Layers carry structural metadata (block name, element, variation) rather than a pre-built string, so the UI composes and translates the labels — the engine stays i18n-agnostic.Labels describe scope rather than structure, so reading top to bottom narrows: Site-wide → All Paragraph blocks → Subtitle style → Just this block → Custom CSS.
Two things the cascade alone could not explain:
style.css(top-level declarations only; nested selectors target other states).cssoutright viaTREE_STRUCTURAL_KEYS. Rather than assert a winner the page contradicts, the popover compares the winning value against the element's computed style and says so when they disagree.Also adopted from the #80649 review:
Testing Instructions
font-size: 7rem !importantunder Advanced → Custom CSS. It appears as its own layer, and the diamond shows even when custom CSS is the only override.Known gaps
Stated plainly, since this is a draft:
clamp(), and custom CSS bypasses that pipeline entirely. Both produced false positives before being handled. The property allowlist excludes the obviously messy cases but has not been audited property by property;spacing.*andborder.*in particular are unverified.getByRole('button', { name: /Color/ })ambiguous, since it now matches both the control and its indicator. One existing test needed anchoring. Any contributor querying controls by property name will hit this.opacity: 0at rest), inherited from when that slot held a reset button. Every other property signals its override at rest; colour does not.titleattribute used for full values is not keyboard or touch accessible; it wants a realTooltip.CHANGELOG.mdentries, deliberately — this is not proposed for merge in its current shape.AI disclosure