Skip to content

START

Page transitions

One import, one attribute, and your navigations animate instead of blinking. No JavaScript, and nothing to fall back to.

The browser does all of it. Two pages that both opt in get captured, cross-faded and swapped by the compositor β€” no router, no script, no intercepted clicks. A browser that has never heard of any of this navigates the way it always did, and your reader loses an animation rather than a page.

THESE THREE LINKS, AND NOT THE LEFT RAIL

The rail is a client router: it fetches the next page and swaps it in, so the document never changes and this feature never runs. The three links below are marked data-astro-reload, which hands them back to the browser β€” a real navigation, two real documents, and the library doing the whole thing with no script involved. Press one, then press something in the rail, and you can feel the difference.

Switching it on

It is a subpath, not part of the main import, because @view-transition changes how every navigation on a site behaves and a design system should not switch that on because somebody imported a button. Both the page being left and the page being entered have to opt in, which one import in one stylesheet takes care of.

/* after the main import β€” see Install for the whole entry file */
@import "@pipejesus/higreg-ds";
@import "@pipejesus/higreg-ds/transitions";   /* ← this one */

Choosing an effect

One attribute on <html>. Leave it off and the page cross-fades; set it to paper or turn and it arrives that way.

<html data-ds-transition="paper">
  …
</html>

IT GOES ON THE PAGE YOU ARE ARRIVING AT

Not on the one you are leaving, and this is the API rather than a quirk of this library. In a cross-document transition the old page is a snapshot and every pseudo-element is built in β€” and styled by β€” the document being navigated to. So a page decides how it ARRIVES, and cannot decide how it leaves.

The three effects

FADEThe default. Opacity only, 180ms, nothing moves. It is also what everybody gets under reduced motion.
PAPERA fresh sheet laid over the last one: the outgoing page settles back and dims, the incoming one rises 32px into place under an edge shadow.
TURNA page turned on its spine. Both halves swing about the left edge under a shared perspective, stopping well short of edge-on β€” there is no back.

Reduced motion is the floor

Everything that moves lives inside prefers-reduced-motion: no-preference. A reader who has asked for less motion gets the cross-fade and nothing else β€” not a shorter slide, no slide. A fade moves nothing and is what the browser would have done unaided, which is why it is the base case rather than a fourth effect somebody has to remember to select.

Changing one

The durations, the lift, the angle and the shadow are all tokens β€” --ds-vt-fade, --ds-vt-sheet, --ds-vt-turn, --ds-vt-lift, --ds-vt-angle, --ds-vt-depth and --ds-vt-sheet-shadow β€” so a re-theme moves them. To change an animation outright, write an unlayered rule after the import: nothing in the library or anything it imports targets these pseudo-elements, so there is no specificity to fight.

/* Unlayered, like the library's own, so source order decides it. */
[data-ds-transition="paper"]::view-transition-new(root) {
  animation-duration: 500ms;
}

WHERE IT WORKS, MEASURED

Cross-document transitions are Chrome 126 and up and Safari 18.2 and up. Firefox does not ship them yet, so roughly one reader in four navigates instantly and sees no difference. There is nothing to write for that case: the at-rule is ignored, and no box on the page is positioned, sized or painted differently because of it.

WHY THERE IS NO PER-LINK MARKER

Choosing an effect from the link that was pressed means view-transition types, and types set in pageswap on the outgoing page are explicitly not carried to the incoming one β€” the destination has to set them itself, in pagereveal, which fires before the first rendering opportunity and therefore has to be listened for from a parser-blocking classic script in <head>.

That is a render-blocking script so a decoration can pick between two fades, on a system whose whole posture is that the script is an enhancement and never the mechanism. It was tried, and refused. The attribute does the same job in the markup, correctly, every time.

Reference

Read from transitions.css when this page was built, so it cannot fall behind the stylesheet.

CLASSES
READS--ds-vt-angle--ds-vt-depth--ds-vt-ease--ds-vt-fade--ds-vt-lift--ds-vt-sheet--ds-vt-sheet-shadow--ds-vt-turn--ds-vt-turn-shadow