Skip to content

fix(transition): make large title animations work in every theme - #31559

Open
thetaPC wants to merge 17 commits into
FW-6863from
FW-6863-animations
Open

thetaPC wants to merge 17 commits into
FW-6863from
FW-6863-animations

Conversation

@thetaPC

@thetaPC thetaPC commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Issue number: resolves internal


What is the current behavior?

The iOS large title animations rely on the iOS theme's title stylesheet, so they only look right in the iOS theme. With iOS mode and the md or ionic theme, the page transition squashes the title (width scale 0.20 instead of about 0.5), a centered title jumps to the screen edge, and the overscroll stretch slides the title 17px past its padding.

What is the new behavior?

  • The iOS page transition sets up what it needs itself: it measures the title's text, copies the title's color, font and padding to its copy, starts both copies where the text is rendered so a centered title morphs in place, and hides the back button with visibility so a title placed next to it doesn't shift.
  • The header stretch sets its own transform origin from the title's text alignment.
  • Removes the title's animation-only tokens and the cloned title rule, which nothing reads anymore.
  • Fixes the ionic toolbar briefly showing a centered title off-center while the back button renders.
  • Adds a title transition test page.

This is the first proof of concept for FW-7295: animations set up what they need instead of relying on a theme's stylesheet.

Does this introduce a breaking change?

  • Yes
  • No

Other information

Open each before and after link side by side. For the pull down rows, use Safari on a Mac with a trackpad in Responsive Design Mode for a phone-sized view, or the iOS Simulator. With the page at the top, do a two-finger swipe down over the page content so it bounces past the top. Chrome and Firefox, including their device modes, can't scroll past the top, so the title won't grow there.

Check Before After What to do and look for
Page transition, ionic theme before after Tap "Go (matching back text)", then the back button. Before, the centered big title jumps to the left edge and gets squeezed narrow on its way to the back button. After, it shrinks and slides from where it sits, and grows back the same way
Page transition, md theme before after Tap "Go (matching back text)", then the back button. Before, the big title doesn't move to the back button. After, it shrinks and slides into it, and grows back the same way
Page transition, ios theme before after Tap "Go (matching back text)", then the back button. Looks the same on both
Toolbar centering before after Tap "Go (matching back text)". Before, the "Message" title appears right of center for a moment, then jumps to the center. After, it's centered from the start
Pull down, md theme before after Pull down at the top of the page. Before, the growing title's left edge moves past the page margin. After, the left edge stays in place
Pull down, md theme, RTL before after Same as above, on the right edge
Pull down, ios theme before after Pull down at the top of the page. Looks the same on both
Pull down, ionic theme before after Pull down at the top of the page. Looks the same on both

@thetaPC
thetaPC added this pull request to stack #31538 October 9, 2026 21:46
@vercel

vercel Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
ionic-framework Ready Ready Preview Oct 22, 2026 6:18pm UTC

Request Review

@github-actions github-actions Bot added the package: core @ionic/core package label Oct 9, 2026
@thetaPC thetaPC changed the title Fw 6863 animations fix(transition): make large title animations work in every theme Oct 9, 2026
@thetaPC
thetaPC force-pushed the FW-6863-animations branch from 5974626 to 04dc471 Compare October 10, 2026 02:29
@thetaPC
thetaPC force-pushed the FW-6863-animations branch from 04dc471 to fe6013d Compare October 12, 2026 15:15
@thetaPC
thetaPC marked this pull request as ready for review October 12, 2026 18:14
@thetaPC
thetaPC requested a review from a team as a code owner October 12, 2026 18:14
@thetaPC
thetaPC requested a review from brandyscarney October 12, 2026 18:14

@brandyscarney brandyscarney left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a few minor requests but this is looking good!

Comment thread core/src/components/title/test/transition/index.html
Comment thread core/src/components/header/header.utils.ts
Comment thread core/src/components/toolbar/toolbar.tsx
Comment thread core/src/components/toolbar/toolbar.tsx
Comment thread core/src/components/header/header.utils.ts Outdated
Comment thread core/src/components/title/test/transition/title.e2e.ts
# Conflicts:
#	core/api.txt
#	core/src/components/title/title.scss

@brandyscarney brandyscarney left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, a few requests but nothing major!

Comment thread core/src/utils/transition/ios.transition.ts
Comment on lines +39 to +64
/**
* The large title grows from the edge its text is aligned to, so the
* text's start doesn't shift. Centered text grows from the center.
*
* @internal
* @param ionTitleEl - The title whose alignment and direction set the origin.
* @returns The horizontal `transform-origin` keyword: `left`, `right` or `center`.
*/
const getTitleOrigin = (ionTitleEl: HTMLIonTitleElement): string => {
const { textAlign, direction } = getComputedStyle(ionTitleEl);
const rtl = direction === 'rtl';

if (textAlign === 'center' || textAlign === '-webkit-center') {
return 'center';
}

if (textAlign === 'left' || textAlign === 'right') {
return textAlign;
}

if (textAlign === 'end') {
return rtl ? 'left' : 'right';
}

return rtl ? 'right' : 'left';
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing tests getTitleOrigin automatically. Could you add a spec covering start, end, left, right and center in LTR and RTL? Also, since it’s cached at index creation, setting size="large" later writes undefined center. Would working it out lazily in scaleLargeTitles avoid that?

Comment thread core/src/components/toolbar/test/title-placement/toolbar.e2e.ts
# Conflicts:
#	core/src/components/title/title.interface.ts

@ShaneK ShaneK left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, great work! Just a few comments, mostly around test coverage, so feel free to take or leave them.

* How far the drawn text sits from where the shrunk box starts: 0 for a
* start-aligned title, more for a centered one. The copies start there.
*/
const startShift = Math.max(0, rtl ? box.right - text.right : text.left - box.left);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const startShift = Math.max(0, rtl ? box.right - text.right : text.left - box.left);
const startShift = text.width === 0 ? 0 : Math.max(0, rtl ? box.right - text.right : text.left - box.left);

When the large title is empty, the range rect is all zeros, so in RTL this comes out as box.right and the back button copy starts on the wrong side of the screen. With "Back" as the back text, its text starts around x = 1 in ios and -32 in ionic instead of near 314. Falling back to 0 puts it back on the right, and the transition tests still pass.

await expect(page.locator('page-inbox ion-title[size="large"]')).toHaveClass(/hydrated/);
};

test(`should start the large title copy on the title text in the ${theme} theme`, async ({ page }) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both position tests stop on the start keyframe, so removing largeTitleStartShift from both END_TRANSLATE_X branches and from CONTAINER_START_TRANSLATE_X still passes everything here. I tried an exact end check, but it's 7-11px off in md and ionic even on this branch, which makes sense since the copies only roughly overlap and the forward copy has faded out by then. Do you think a check with a loose tolerance is worth adding?


[
{ label: 'default', color: undefined, hue: undefined },
{ label: 'bold primary', color: 'primary', hue: 'bold' },

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this test checks the font copy at all, since removing only font-family still passes everything. Removing color too still passes bold primary in every theme, because the copy already gets color and falls back to the bold hue. Could a case where the toolbar uses a different font than body cover it?

scaleLargeTitles(headerIndex.toolbars, 1.1);

const innerTitle = title.shadowRoot!.querySelector<HTMLElement>('.toolbar-title')!;
expect(innerTitle.style.transformOrigin).not.toContain('undefined');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
expect(innerTitle.style.transformOrigin).not.toContain('undefined');
expect(innerTitle.style.transformOrigin).toBe('left center');

This passes on an empty string too, so it wouldn't catch scaleLargeTitles skipping the write. Mock-doc has no textAlign, so it's always left center here, and this version fails both with the write removed and with the old undefined center caching.

'padding-right': paddingRight,
'padding-bottom': paddingBottom,
'padding-left': paddingLeft,
'pointer-events': 'none',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'pointer-events': 'none',

I don't think this one does anything. The host already has pointer-events: none from the stylesheet and .toolbar-title sets it back to auto, so the copy's text takes hits either way. If the copy should be inert like the back button copy, setting it on the copy's .toolbar-title next to the width: auto write does that.

* so a centered title never shows off-center. Runs again on slotchange
* to watch content added later.
*/
private observeSlotContent() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since getTitlePlacement() returns center for ios too, every ios theme toolbar gets this observer, but nothing there reads the classes or --start-end-size. The slots are display: contents in ios, so they always measure 0 and the variable never gets set. Is that fine to leave until FW-6864, or should it be limited to ionic for now?

This branch was successfully deployed

1 active deployment
Preview — c9886e7d Deployed Oct 22, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

package: core @ionic/core package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants