Skip to content

Block Supports: Add translate property on X and Y axis to Transform supports - #86282

Draft
aaronrobertshaw wants to merge 8 commits into
add/transform-block-supportfrom
add/transform-translate
Draft

aaronrobertshaw wants to merge 8 commits into
add/transform-block-supportfrom
add/transform-translate

Conversation

@aaronrobertshaw

@aaronrobertshaw aaronrobertshaw commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Important

This builds on #86240 and should merge after it. Its base is that PR's branch.

Related:

What?

Adds an X and Y offset to the Transform block support, through the CSS translate property. It combines with rotation. Group, Heading, Image and Video opt in.

Why?

Blocks cannot be nudged away from where the layout puts them. The grid canvas work in #85505, where blocks are placed freely, needs this too.

How?

  • Value: style.transform.translate is { x, y }. Each axis is one length or percentage.
  • Output: the translate property, never transform.
  • One axis: a block that sets only X keeps Y from Global Styles, and the reverse. A state such as @mobile fills from the block first. Otherwise the axis is 0. This runs at output, never at save.
  • Validation: style engine, theme.json and KSES drop calc(), var(), lists and other values.
  • Unknown keys: a z key fails the schema and PHP drops it.
  • UI: X offset and Y offset fields, negatives allowed. Each resets on its own.
  • Same as rotation: states, Global Styles, settings and skip serialization. Image and Video move as a whole, caption included.

Z is left out. It needs perspective on a parent, so it can follow later as one more key.

Note

Known gap: a Global Styles variation or state that sets one axis still outputs the other as 0.

"core/heading": {
	"transform": { "translate": { "x": "3rem" } }
}

Testing Instructions

  1. Add an Image with a caption and lightbox. Set Styles > Transform > X offset and Y offset.
  2. The image and its caption move, in the editor and front end. Add a rotation; both apply. The lightbox image has neither.
  3. On a Heading, choose Mobile, turn on Responsive styles and set an offset. Desktop stays in place.
  4. Add the theme.json value above. Global Styles shows 3rem, and every Heading moves.
  5. On one Heading, set only Y offset 1rem. It keeps X 3rem, and the inspector shows it.

Screenshots or screencast

translate.mp4

Use of AI Tools

Written with Claude Code, then reviewed and tested.

@aaronrobertshaw aaronrobertshaw added [Type] Enhancement A suggestion for improvement. [Feature] Design Tools Tools that impact the appearance of blocks both to expand the number of tools and improve the experi labels Nov 4, 2026
@aaronrobertshaw aaronrobertshaw self-assigned this Nov 4, 2026
@github-actions github-actions Bot added [Package] Blocks /packages/blocks [Package] Block library /packages/block-library [Package] Block editor /packages/block-editor [Package] Style Engine /packages/style-engine labels Nov 4, 2026
@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

🤖 PR meta 🤖

Preview in WordPress Playground Preview in Storybook

📦 Bundle size

Size Change: +1.09 kB (+0.01%)

Total Size: 8.64 MB

📦 View Changed
Filename Size Change
build/modules/lazy-editor/index.min.js 14.6 kB +5 B (+0.03%)
build/scripts/block-editor/index.min.js 547 kB +838 B (+0.15%)
build/scripts/block-library/index.min.js 425 kB +14 B (0%)
build/scripts/blocks/index.min.js 45 kB +11 B (+0.02%)
build/scripts/edit-site/index.min.js 379 kB +5 B (0%)
build/scripts/editor/index.min.js 568 kB +3 B (0%)
build/scripts/style-engine/index.min.js 2.5 kB +146 B (+6.2%) 🔍
build/styles/block-editor/style-rtl.css 20.1 kB +8 B (+0.04%)
build/styles/block-editor/style-rtl.min.css 17.1 kB +6 B (+0.04%)
build/styles/block-editor/style.css 20.1 kB +10 B (+0.05%)
build/styles/block-editor/style.min.css 17.1 kB +7 B (+0.04%)
build/styles/block-library/image/style-rtl.css 3.03 kB +5 B (+0.17%)
build/styles/block-library/image/style-rtl.min.css 1.97 kB +5 B (+0.25%)
build/styles/block-library/image/style.css 3.04 kB +4 B (+0.13%)
build/styles/block-library/image/style.min.css 1.96 kB +1 B (+0.05%)
build/styles/block-library/style-rtl.css 22.9 kB +5 B (+0.02%)
build/styles/block-library/style-rtl.min.css 19.4 kB +4 B (+0.02%)
build/styles/block-library/style.css 23 kB +5 B (+0.02%)
build/styles/block-library/style.min.css 19.4 kB +4 B (+0.02%)

a17eee9 Run

⚡ Performance

Show the results

Client side metrics exclude the server response time.

front-end-block-theme

Metric 7659230 add/transform-block-support % Change
timeToFirstByte 56.5 ms +3.27% -2.04% 56.3 ms +5.06% -4.53% 0.36%
largestContentfulPaint 88 ms +6.82% -4.55% 88 ms +6.82% -4.55% 0%
lcpMinusTtfb 29.45 ms +25.13% -5.6% 29.45 ms +29.71% -5.43% 0%
wpBeforeTemplate 29.42 ms +6.08% -1.9% 29.47 ms +10.49% -4.92% -0.17%
wpTemplate 22.92 ms +1.4% -1.96% 22.36 ms +5.77% -4.96% 2.5%
wpTotal 52.65 ms +3.32% -2.47% 52.45 ms +5.36% -4.71% 0.38%
wpMemoryUsage 7.72 MB +0% -0% 7.71 MB +0% -0% 0.12%
wpDbQueries 16 +0% -0% 16 +0% -0% 0%

front-end-classic-theme

Metric 7659230 add/transform-block-support % Change
timeToFirstByte 45.55 ms +12.62% -0.66% 46.25 ms +8.76% -1.41% -1.51%
largestContentfulPaint 84 ms +4.76% -4.76% 84 ms +2.38% -4.76% 0%
lcpMinusTtfb 34.85 ms +5.45% -0.86% 34.7 ms +8.21% -2.16% 0.43%
wpBeforeTemplate 26.22 ms +14.04% -1.3% 26.41 ms +8.07% -2.01% -0.72%
wpTemplate 16.45 ms +6.93% -3.34% 16.22 ms +3.58% -0.92% 1.42%
wpTotal 42.52 ms +12.14% -0.96% 43.1 ms +8.72% -1.65% -1.35%
wpMemoryUsage 6.41 MB +0% -0% 6.40 MB +0% -0% 0.15%
wpDbQueries 13 +0% -0% 13 +0% -0% 0%

media-processing

Metric 7659230 add/transform-block-support % Change
mediaProcessingJpeg 398.73 ms +1.11% -0.21% 399.12 ms +1.41% -0.51% -0.1%
mediaProcessingAvif 6172.56 ms +0.34% -0.47% 6170.85 ms +0.43% -0.29% 0.03%
mediaProcessingJpegToAvif 4259.91 ms +0.34% -0.39% 4236.56 ms +0.35% -0.13% 0.55%

media-upload

Metric 7659230 add/transform-block-support % Change
jpegUploadProcessing 2028.74 ms +1.29% -1.98% 1979.52 ms +0.04% -22.87% 2.49%
pngUploadProcessing 270.43 ms +3.15% -2.95% 278.56 ms +2.99% -4.75% -2.92%
largeJpegUploadProcessing 1552.95 ms +27.53% -3.02% 1506.94 ms +29.86% -0.97% 3.05%
multipleImageUploadProcessing 2096.31 ms +1.86% -0.31% 2048.19 ms +0.68% -0.85% 2.35%

post-editor

Metric 7659230 add/transform-block-support % Change
serverResponse 290.26 ms +3.54% -6.28% 295.61 ms +1.79% -7.38% -1.81%
firstPaint 311.13 ms +4.89% -5.71% 315.68 ms +3.79% -2.21% -1.44%
domContentLoaded 608.31 ms +5.12% -4.77% 619.27 ms +0.51% -2.03% -1.77%
loaded 609.25 ms +5.12% -4.76% 620.24 ms +0.53% -2.02% -1.77%
firstContentfulPaint 346.98 ms +8.11% -5.95% 351.04 ms +1.97% -1.76% -1.16%
firstBlock 1602.39 ms +1.58% -2.26% 1642.24 ms +1.71% -1.26% -2.43%
type 14.64 ms +19.13% -7.86% 16.96 ms +5.25% -5.72% -13.68%
typeWithoutInspector 15.12 ms +10.05% -5.69% 15.91 ms +7.67% -8.99% -4.97%
typeWithTopToolbar 19.25 ms +4.16% -10.34% 21.8 ms +4.4% -7.2% -11.7%
typeContainer 6.79 ms +7.07% -7.22% 7.24 ms +5.39% -15.06% -6.22%
focus 91.72 ms +2.25% -8.36% 90.07 ms +1.85% -4.33% 1.83%
firstFocus 197.37 ms +0% -0% 187.79 ms +0% -0% 5.1%
selectAll 287.51 ms +1.91% -3.69% 277.41 ms +7.2% -2.38% 3.64%
undo 24.05 ms +11.39% -4.62% 24.41 ms +4.14% -8.81% -1.47%
listViewOpen 54.78 ms +4.71% -2.25% 56.59 ms +13.01% -3.78% -3.2%
inserterOpen 30.47 ms +14.9% -7.09% 33.73 ms +13.96% -5.57% -9.66%
inserterHover 9.6 ms +7.71% -7.92% 10.54 ms +5.22% -6.26% -8.92%
inserterSearch 7.18 ms +6.13% -10.58% 7.21 ms +9.57% -2.5% -0.42%
loadPatterns 499.9 ms +3.19% -7.53% 508.86 ms +4.37% -5.86% -1.76%
wpTotal 283.23 ms +3.51% -6.61% 288.49 ms +1.76% -7.69% -1.82%
wpMemoryUsage 14.78 MB +0% -0% 14.75 MB +0% -0% 0.18%
wpDbQueries 55 +0% -0% 55 +0% -0% 0%

site-editor

Metric 7659230 add/transform-block-support % Change
serverResponse 313.61 ms +1.79% -8.45% 316.26 ms +1.17% -9.85% -0.84%
firstPaint 365.06 ms +1.46% -11.02% 337.43 ms +10.03% -7.75% 8.19%
domContentLoaded 645.11 ms +6.45% -0.82% 642.76 ms +1.44% -0.68% 0.37%
loaded 645.9 ms +6.47% -0.83% 643.57 ms +1.43% -0.68% 0.36%
firstContentfulPaint 373.45 ms +6.87% -2.98% 369.8 ms +1.31% -2.72% 0.99%
firstBlock 1948.37 ms +6.84% -0.33% 1948.45 ms +0.93% -1.02% 0%
type 15.32 ms +5.87% -11.1% 14.43 ms +2.01% -8.25% 6.17%
navigate 85.66 ms +11.13% -5.97% 90.43 ms +4.05% -1.51% -5.27%
loadPatterns 1112.68 ms +10.56% -5.77% 1099.17 ms +8.73% -20.63% 1.23%
loadPages 989.59 ms +0.65% -1.88% 1001.5 ms +4.1% -1.06% -1.19%
wpTotal 305.28 ms +1.84% -8.66% 308 ms +1.19% -10.11% -0.88%
wpMemoryUsage 12.79 MB +0% -0% 12.79 MB +0% -0% 0.05%
wpDbQueries 45 +0% -0% 45 +0% -0% 0%

a17eee9 Run

🏁 Flaky tests

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.

Page List converts to Navigation Links with entity bindings in /test/e2e/specs/editor/blocks/navigation.spec.js, passed after 1 failed attempt.
Error: expect(locator).toContainText(expected) failed

Locator: getByRole('region', { name: 'Editor settings' }).getByRole('tabpanel', { name: 'Content' }).getByRole('button', { name: /Link to:/ })
Expected substring: "test-page-1"
Received string:    "Link to:Cat/catPagePublished"
Timeout: 5000ms

Call log:
  - Expect "toContainText" getByRole('region', { name: 'Editor settings' }).getByRole('tabpanel', { name: 'Content' }).getByRole('button', { name: /Link to:/ }) with timeout 5000ms
  - waiting for getByRole('region', { name: 'Editor settings' }).getByRole('tabpanel', { name: 'Content' }).getByRole('button', { name: /Link to:/ })
    14 × locator resolved to <button type="button" aria-expanded="false" aria-haspopup="dialog" class="components-button link-preview-button is-next-40px-default-size is-secondary">…</button>
       - unexpected value "Link to:Cat/catPagePublished"

    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/blocks/navigation.spec.js:1949:31
should place the caret at the click position when clicking a block that is not selected in /test/e2e/specs/editor/various/writing-flow.spec.js, passed after 1 failed attempt.
Error: expect(received).toMatchObject(expected)

- Expected  - 1
+ Received  + 1

@@ -1,9 +1,9 @@
  Array [
    Object {
      "attributes": Object {
-       "content": "First!",
+       "content": "First",
      },
      "name": "core/paragraph",
    },
    Object {
      "attributes": Object {

Call Log:
- Timeout 5000ms exceeded while waiting on the predicate
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/various/writing-flow.spec.js:952:41

a17eee9 Run

@aaronrobertshaw aaronrobertshaw changed the title Transform: Add X and Y offsets with the translate property Block Supports: Add translate property on X and Y axis to Transform supports Nov 4, 2026
@aaronrobertshaw
aaronrobertshaw force-pushed the add/transform-block-support branch from a49cb25 to 45793e8 Compare November 9, 2026 11:34
@aaronrobertshaw
aaronrobertshaw force-pushed the add/transform-translate branch 2 times, most recently from d765b3a to 3256c31 Compare November 10, 2026 17:03
@aaronrobertshaw
aaronrobertshaw force-pushed the add/transform-block-support branch from 4584d08 to acaae5c Compare November 11, 2026 03:51
@aaronrobertshaw
aaronrobertshaw force-pushed the add/transform-block-support branch from acaae5c to 5132186 Compare November 11, 2026 07:42
Store style.transform.translate as an object with x and y axes and
output the individual translate property, validated in the style engine,
theme.json and KSES. Add X offset and Y offset controls to the Transform
panel and opt in Group, Heading, Image and Video.
Cover the style engine, theme.json, KSES, the block support, block
states, the Transform panel and global styles rendering. Extend the e2e
spec with an Image offset, its caption and lightbox, and a Mobile offset
on a Heading.
…_html

Removing insecure properties skipped every array value, so a safe translate object was dropped on save. It is now kept when its declaration passes, which only happens when every axis is valid.
CSS translate holds both axes, so a block that set only Y reset a Global
Styles X to 0 while its inspector still showed X. The block now fills its
missing axis when it is output, from the resolved Global Styles value. A
state such as @mobile fills first from the block's own value. Nothing is
copied into the saved attributes, so later Global Styles changes apply.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Feature] Design Tools Tools that impact the appearance of blocks both to expand the number of tools and improve the experi [Package] Block editor /packages/block-editor [Package] Block library /packages/block-library [Package] Blocks /packages/blocks [Package] Style Engine /packages/style-engine [Type] Enhancement A suggestion for improvement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant