Skip to content

Refine the admin menu view transition animation#11912

Open
westonruter wants to merge 3 commits into
WordPress:trunkfrom
westonruter:trac-65032-view-transition-improvements
Open

Refine the admin menu view transition animation#11912
westonruter wants to merge 3 commits into
WordPress:trunkfrom
westonruter:trac-65032-view-transition-improvements

Conversation

@westonruter
Copy link
Copy Markdown
Member

@westonruter westonruter commented May 20, 2026

Trac ticket: https://core.trac.wordpress.org/ticket/65032

When view transitions were enabled throughout wp-admin in 7.0, each top-level admin menu item was given a stable view-transition-name, so navigating between sections animates them into place (the "accordion" effect). Three rough edges remained:

  • The accordion failed intermittently. A cross-document view transition snapshots the incoming page at its first render, which can occur before the <body> has finished parsing. The snapshot then captures a half-built admin menu, so the menu items have no height delta and the accordion silently degrades to a plain cross-fade. It reproduces most easily with DevTools closed; pagereveal instrumentation confirmed the snapshot being taken at readyState: "loading" with an incomplete menu.
  • A newly-current section's submenu was captured at its full intrinsic height, so it painted at full height from the first frame and overlapped the menu items below while they were still sliding down into place.
  • The collapse button (#collapse-menu) was not named, so it cross-faded as part of the page root instead of moving with the rest of the menu.

Changes

  • Print a render-blocking <link rel="expect" href="#wpfooter" blocking="render" media="(prefers-reduced-motion: no-preference)"> in the admin <head>, so the first render — and therefore the transition's snapshot — waits until the page is fully parsed. The snapshot then always captures a complete menu and the accordion runs reliably. The media query scopes the small first-render delay to when view transitions actually run.
  • Add a shared view-transition-class (wp-menu-top) to every top-level menu item and set overflow: clip on ::view-transition-image-pair(.wp-menu-top). This clips each menu-top's snapshot to its animating group box, so an expanding submenu wipes open from the link row to full height — and wipes closed in reverse — in step with the items below it, instead of overlapping them.
  • Give #collapse-menu its own view-transition-name so it slides into place with the menu items rather than cross-fading via the root.

Files changed: src/wp-admin/css/view-transitions.css, src/wp-includes/view-transitions.php, src/wp-includes/default-filters.php.

Testing instructions

  1. In Chrome, with the OS "reduced motion" setting off, log in to wp-admin.
  2. Navigate repeatedly between sections whose submenus differ — e.g. Settings ↔ Users — including with DevTools closed. The section accordion (the previous submenu wiping closed, the new one wiping open) should play on every navigation, never degrading to a plain cross-fade.
  3. Confirm the expanding submenu wipes open in step with the surrounding menu items, rather than briefly overlapping the items beneath it.
  4. Confirm the "Collapse menu" button slides with the menu rather than fading.

Clip each top-level menu item's view transition snapshot to its
animating group box. By default a captured snapshot keeps its full
intrinsic height, so a newly expanded submenu painted at full height
from the first frame and overlapped the menu items below while they
were still sliding into place. Setting `overflow: clip` on the image
pair makes the submenu wipe open, and closed in reverse, in step with
those items.

Also name the collapse button so it slides into place with the menu
items rather than cross-fading via the page root, and add comments
explaining each rule.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props westonruter.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions
Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

@westonruter
Copy link
Copy Markdown
Member Author

In the following videos, I have a plugin active which slows down the animations to make them more observable:

add_action(
	'admin_print_styles',
	function () {
		?>
		<style>
			/* TEMP DEBUG: slow every view transition animation down to 2s. Remove before shipping. */
			::view-transition-group(*),
			::view-transition-old(*),
			::view-transition-new(*) {
				animation-duration: 1s;
			}
		</style>
		<?php
	}
);

Before:

Screen.Recording.2026-05-20.at.16.26.37.mov

After:

Screen.Recording.2026-05-20.at.16.28.04.mov

A cross-document view transition snapshots the incoming page at its
first render, which can occur before the body has finished parsing.
When that happens the admin menu is only partially built in the
snapshot, so the menu-top elements have no height delta and the
section accordion silently degrades to a plain cross-fade.

Print a render-blocking `<link rel="expect">` pointed at the end of
the body, so the first render — and therefore the transition's
snapshot — waits until the page is fully parsed. The media query
scopes the small first-render delay to when view transitions run.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@westonruter
Copy link
Copy Markdown
Member Author

🤖 From Claude Code — paired with @westonruter on this branch. Posting this to document how the root cause was found, since the render-blocking fix isn't obvious without the backstory.

Diagnosing the intermittent accordion failure

Symptom. Navigating between admin sections, the menu "accordion" (one section's submenu collapsing as the next expands) usually played, but intermittently degraded to a plain cross-fade with no movement. It was hard to reproduce — and, crucially, it happened more often with DevTools closed.

Ruled out along the way:

  • Scroll position. The admin menu scrolls with the page when it is taller than the viewport, so a scroll delta between two loads can translate the whole menu. This was confirmed as a real mechanism via a controlled same-document transition — but the bug also occurs with a fixed, top-aligned menu, so it was not the cause here.
  • Overlapping navigations. Clicking again before the previous (~0.25s) transition finished could interrupt it. Instrumentation ruled this out: the failing navigations were clean push navigations with priorTransitionStillRunning: false.

Instrumentation. A temporary pagereveal / pageswap logger recorded, for every navigation, each menu item's captured old/new view-transition snapshot heights plus context (readyState, navigation type, whether a prior transition was still running). Because the bug reproduced best with DevTools closed, each failing navigation was persisted to localStorage so it could be captured without DevTools open and inspected afterward.

Root cause. Every captured failure showed the same two things: readyState: "loading", and an incomplete admin menu — e.g. only 7 of the 11 .menu-top items existed, or none at all.

A cross-document view transition snapshots the incoming page at its first render, and the browser paints incrementally — so first paint (and therefore the snapshot) can happen before the <body> has finished parsing. When it does, the admin menu is only half-built in the snapshot; the items that should change height are not there yet, so there is no geometry delta to animate and the transition falls back to a plain cross-fade. "More often with DevTools closed" fits exactly: a faster load lets first paint beat the body parse.

Fix. A render-blocking expectation in the admin <head>:

<link rel="expect" href="#wpfooter" blocking="render" media="(prefers-reduced-motion: no-preference)">

This holds the first render — and so the view transition's snapshot — until #wpfooter (the end of the body) has parsed, guaranteeing the snapshot captures a fully-built page. The media query confines the small first-render delay to when view transitions actually run.

The two CSS refinements in the first commit — clipping each menu item's snapshot so a submenu wipes open/closed instead of overlapping the items below, and naming #collapse-menu so it moves with the menu — were found and fixed during the same investigation.

…hould return string but returns string|false.
@westonruter westonruter requested a review from adamziel May 21, 2026 00:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant