Skip to content

Global Styles: Show the inherited-from breadcrumb in the override indicator tooltip - #80757

Draft
annezazu wants to merge 21 commits into
WordPress:trunkfrom
annezazu:add/inheritance-override-breadcrumb
Draft

Global Styles: Show the inherited-from breadcrumb in the override indicator tooltip#80757
annezazu wants to merge 21 commits into
WordPress:trunkfrom
annezazu:add/inheritance-override-breadcrumb

Conversation

@annezazu

Copy link
Copy Markdown
Contributor

What?

Adds a Global Styles breadcrumb to the override-indicator tooltip in the block inspector. When a control locally overrides a value inherited from Global Styles, the tooltip now names where that inherited value comes from — e.g.:

Overrides inherited styles from Blocks › Group › Variations › Subtitle

  • Root/default-sourced overrides and the Global Styles screens keep the bare Overrides inherited styles.
  • Compound controls with mixed sources read Overrides inherited styles from multiple sources.
  • Nothing changes at rest — inheritance stays unmarked.

Important

Builds on #80649 (the override-indicator iteration). Until that lands, this PR's diff includes #80649's commits; review the last commit (Global Styles: Show the inherited-from breadcrumb…) for the breadcrumb change in isolation. Rebase once #80649 merges.

Why?

#80649 marks that a control overrides an inherited value, but not what it overrides. The breadcrumb closes that gap without leaving the inspector, reviving the intent of the earlier (closed) explorations in #79992 / #80150.

How?

Follows the approach settled on in #80150 (which superseded the #79992 createPortal DOM hack): breadcrumb part identifiers are recorded per source at resolve time and translated to titles in the block-editor layer — but rendered inside the existing diamond tooltip rather than a separate label tooltip, so no component public APIs change.

  • @wordpress/global-styles-engine (resolve-style.ts): each source-map entry now carries { breadcrumb, blockName, variation }. Block-agnostic — it records generic part identifiers plus the slugs it already receives; no title resolution in the engine.
  • @wordpress/block-editor (global-styles/inheritance/index.js): translation helpers (getTranslatedBreadcrumb, getOverrideTooltipText, getCommonOverrideTooltipText) resolve titles via getBlockType/registered block styles and drop the redundant root Styles node; InheritanceOverrideIndicator renders the result. The source map is threaded through the block-supports hooks to every panel and bespoke control.

Wired everywhere inherited styles surface: Typography (incl. Color), Color (link + per-element), Dimensions (incl. Aspect ratio), Border (incl. Shadow), Background (image, color, gradient), Filters (duotone).

Testing Instructions

  1. Use a block theme with block/variation styles (e.g. Twenty Twenty-Five).
  2. Insert a block that inherits from Global Styles (e.g. a Heading, or a Group with a registered style variation).
  3. Override a control (Typography → Color, Line height, a Border, etc.).
  4. Hover/focus the diamond → the tooltip names the source path.
  5. Confirm the Global Styles screens show no breadcrumb.

Note: spacing/border/dimensions breadcrumbs only appear for values inherited from a block or variation layer (root-level ones don't cascade to blocks by design).

Tests

  • global-styles-engine: resolve-style.test.ts asserts the breadcrumb/blockName/variation metadata per layer.
  • block-editor: inheritance/test/index.js covers the translation helpers and indicator rendering.

🤖 Generated with Claude Code

annezazu and others added 21 commits July 24, 2026 12:15
…values

Iterates on how block-inspector controls signal that a value locally
overrides one inherited from Global Styles (follow-up to WordPress#77894 / WordPress#80506).

- Nothing at rest: an inheriting control shows the inherited value with no
  marker (removes the at-rest dotted-underline label treatment).
- On a local override, a small filled diamond appears next to the control
  with an "Overrides inherited styles" tooltip.
- The diamond is a status indicator only and performs no action; clearing a
  local override relies on the panel's existing reset options.

Replaces the interactive InheritanceResetButton with a shared
InheritanceOverrideIndicator (a Button hosting a filled-diamond SVG wrapped
in a @wordpress/ui Tooltip), used by the standard InheritanceToolsPanelItem
controls and the custom color, gradient, shadow, duotone, and background
image controls.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses review feedback: the diamond's purpose is to reveal the "Overrides
inherited styles" explanation, which is an infotip, not a tooltip (a tooltip is
only for the accessible name of an icon button). Uses @wordpress/ui Popover
with an openOnHover trigger so the content is reachable by hover, keyboard
focus, and tap — a tooltip is unavailable to touch and screen-reader users.

Also fills in the PR number in the CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reverts the infotip in favour of the @wordpress/ui Tooltip, per the
accessibility review on WordPress#80649: the entire message is already the trigger's
accessible name, so the infotip's Popover.Title and Popover.Description only
duplicated it and screen readers announced the same string three times. A
tooltip popup is visual-only, which keeps the aria-label as the single source
of truth.

The trigger goes back to an enabled Button whose only job is to host the
tooltip, so tapping it still performs no action.

Also inlines the Diamond SVG into the indicator and trims the JSDoc, which had
grown to restate the review discussion.

Co-Authored-By: Claude <noreply@anthropic.com>
`InheritanceOverrideIndicator` hand-rolled a Tooltip.Root/Trigger/Popup wrapper
around a Button, which is what Button already does for itself: with a `label`
and no children it renders its own tooltip and mirrors the string into
`aria-label`. Collapsing to a single `<Button label icon />` drops the wrapper,
the duplicated label constant and the `@wordpress/ui` import.

Passing the diamond through the `icon` prop instead of as children lets `Icon`
size the SVG and adds the `has-icon` class, so most of the CSS that existed to
undo the default Button chrome (sizing, padding, background, box-shadow,
cursor, text-transform) is no longer needed. `size="small"` replaces the
`__next40pxDefaultSize={ false }` opt-out and its lint exemption.

Co-Authored-By: Claude <noreply@anthropic.com>
The WordPress#77894 entry had been rewritten in place to describe the final diamond
indicator, which erased the fact that the at-rest treatment shipped as a
dotted underline plus inline reset and only later changed. Restore that entry
to its original wording and add a separate WordPress#80649 entry for removing the
dotted underline and reset affordance in favour of the local-override diamond.

Co-Authored-By: Claude <noreply@anthropic.com>
Drop the redundant `carries no reset action or menu` case (the indicator's
lack of a reset is already covered where it matters) and the explanatory
comments that merely restated what the assertions already show. Also tighten
the background-image `hasLocalOverride` comment.

Co-Authored-By: Claude <noreply@anthropic.com>
Both tests asserted that no "Reset to inherited value" button renders, but the
override indicator no longer has any reset control — that label exists nowhere
in the source, so the assertions were always trivially true. Remove them and
simplify the indicator test to just check the diamond renders.

Co-Authored-By: Claude <noreply@anthropic.com>
Condense the JSDoc and SCSS comments in the inheritance module — they had grown
to restate design rationale and CSS mechanics at length — to the point each
one needs. Also drop the "Reset to inherited value" absence checks from the
border-panel shadow tests; that label no longer exists in the source, so the
assertions were always trivially true. The real "remove" button assertions,
which cover actual behavior, stay.

Co-Authored-By: Claude <noreply@anthropic.com>
The reset button, override indicator and contrast warning each claimed the
same absolutely-positioned slot at the toggle's inline end, so they could
only be shown one at a time and the label's reserved space was hardcoded
per combination. Group them in a single positioned row instead, and derive
the label's max-width from how many buttons that row actually holds, so all
three can coexist and the label always keeps clear of them.

Also make the label a FlexBlock so a long name shrinks itself rather than
squashing the color indicator next to it.

Co-Authored-By: Claude <noreply@anthropic.com>
The reset button and the override indicator shared one absolutely-positioned
slot at the toggle's inline end, so they could only be shown one at a time,
and the toggle reserved a fixed inline-end padding whether or not anything
was rendered there. Group them in a single positioned row instead, and derive
the toggle's padding from how many buttons that row actually holds, so both
can coexist and the label reserves no space when there is no action at all.

This mirrors the layout the color panel now uses.

Co-Authored-By: Claude <noreply@anthropic.com>
The remove button and the override indicator shared one absolutely-positioned
slot at the toggle's inline end, so they could only be shown one at a time.
Group them in a single positioned row instead, and derive the toggle's
inline-end padding from how many buttons that row actually holds, so both can
coexist and no space is reserved when there is no action at all.

The toggle label also had no truncation, so a long translation ran under the
actions row. Render it as a FlexBlock and clip it with an ellipsis, which
keeps the shadow icon from being squashed as well.

This mirrors the layout the color and background image panels now use.

Co-Authored-By: Claude <noreply@anthropic.com>
The reset button and the override indicator shared one absolutely-positioned
slot at the toggle's inline end, so they could only be shown one at a time.
Group them in a single positioned row instead, and derive the toggle's
inline-end padding from how many buttons that row actually holds, so both can
coexist and no space is reserved when there is no action at all.

The toggle label had no truncation styles at all, so a long name ran under the
actions row. Render it as a FlexBlock and clip it with an ellipsis, which
keeps the duotone swatches from being squashed as well.

With this the color, background image, shadow and duotone controls all use the
same layout, so the override indicator no longer borrows any control's reset
slot and the placement overrides it needed can go.

Co-Authored-By: Claude <noreply@anthropic.com>
The toggle carried a -4px top margin that pulled it above the centered
header row, which became visible once the override indicator sat next to
it. The HStack already centers its children, so drop the styled wrapper
and render the Button directly.

Co-Authored-By: Claude <noreply@anthropic.com>
The shadow control no longer renders the override indicator or hides the remove
button for a local override, so these assertions no longer describe the UI.
Remove them, leaving the test as a render smoke check.

Co-Authored-By: Claude <noreply@anthropic.com>
Remove the ENABLE_GLOBAL_STYLES_INHERITANCE flag that gated the inherited
Global Styles treatment on IS_GUTENBERG_PLUGIN. The feature is now enabled in
Core builds: panels default showInheritanceLabelIndicators to true, the
useResolvedStyle cascade merge always runs, and the link-color sync uses the
single inheritance-aware comparison. Drop the -core test files that covered the
now-removed flag-off path.

Co-Authored-By: Claude <noreply@anthropic.com>
Each toggle repeated a per-count comment explaining which buttons the
reserved space was for. The button combinations differ per control and
drift as buttons are added, so state the shared rule once instead: the
actions are absolutely positioned, so the toggle reserves room on the
right for as many of them as are rendered.

Co-Authored-By: Claude <noreply@anthropic.com>
The reset button was the toggle's only trailing control, but the label's
reserved right-hand space is now keyed off an `__actions` element and its
child count, as in the background, shadow and duotone panels. Wrap the
button in a `Stack` carrying that class so the sizing rule applies and
further actions can join the same row.

Co-Authored-By: Claude <noreply@anthropic.com>
The indicator exists only to host its tooltip, so rendering it as a Button
meant suppressing the click it advertised. Render a `role="img"` span with
`tabIndex` through `Tooltip.Trigger` instead: the tooltip stays reachable by
keyboard, but the element carries no interactive semantics. Styles that came
from Button (size, focus ring) move to the span.

Co-Authored-By: Claude <noreply@anthropic.com>
…icator tooltip

Follow-up to WordPress#80649. When a block-inspector control locally overrides a
value inherited from Global Styles, the override indicator's tooltip now
names where that value comes from, e.g. "Overrides inherited styles from
Blocks › Group › Variations › Subtitle". Root/default-sourced overrides and
the Global Styles screens keep the bare "Overrides inherited styles" label.

Follows the approach settled on in WordPress#80150 (superseding the WordPress#79992 portal
hack): breadcrumb part identifiers are recorded per source at resolve time
and translated to titles in the block-editor layer, but rendered inside the
existing diamond tooltip rather than a separate label tooltip.

- global-styles-engine: resolve-style.ts source-map entries carry
  { breadcrumb, blockName, variation } (block-agnostic; no title resolution).
- block-editor: inheritance/index.js adds the translation helpers and renders
  the breadcrumb in InheritanceOverrideIndicator; the source map is threaded
  through the hooks to every panel and bespoke control (Typography, Color,
  Dimensions, Border, Background, Filters, Shadow, Aspect ratio).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Package] Block editor /packages/block-editor [Package] Components /packages/components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants