View Transitions Api
View Transitions API pattern using document.startViewTransition() for SPA route changes — cross-fade default, directional slide override at 350ms cubic-bezier(0.4,0,0.…
$ prime install @community/template-view-transitions-api Projection
Always in _index.xml · the agent never has to ask for this.
ViewTransitionsApi [template] v1.0.0
View Transitions API pattern using document.startViewTransition() for SPA route changes — cross-fade default, directional slide override at 350ms cubic-bezier(0.4,0,0.2,1), and shared-element morph via view-transition-name (Baseline Oct 2025: Chrome 111+, Safari 18+, Firefox 133+).
Loaded when retrieval picks the atom as adjacent / supporting.
ViewTransitionsApi [template] v1.0.0
View Transitions API pattern using document.startViewTransition() for SPA route changes — cross-fade default, directional slide override at 350ms cubic-bezier(0.4,0,0.2,1), and shared-element morph via view-transition-name (Baseline Oct 2025: Chrome 111+, Safari 18+, Firefox 133+).
Language
javascript
Body
<!-- ── Feature detection — required (Firefox < 133 has no support) ──── -->
<script>
/**
* Wrap any DOM mutation in startViewTransition for free cross-fade.
* Pass a feature-detected wrapper to keep consumer code identical.
*/
function withViewTransition(updateDom) {
if (!document.startViewTransition) {
updateDom(); // instant fallback
return;
}
return document.startViewTransition(updateDom);
}
// Async variant (fetches data then updates DOM)
async function navigateTo(href) {
const data = await fetch(href).then(r => r.json());
if (!document.startViewTransition) { renderPage(data); return; }
await document.startViewTransition(async () => {
await renderPage(data);
}).finished;
}
</script>
<!-- ── React / Next.js App Router (Client Component) ──────────────────
"use client";
import { useRouter } from "next/navigation";
import ReactDOM from "react-dom";
export function NavLink({ href, children }) {
const router = useRouter();
return (
<a href={href} onClick={(e) => {
e.preventDefault();
if (!document.startViewTransition) { router.push(href); return; }
document.startViewTransition(() => {
ReactDOM.flushSync(() => router.push(href));
});
}}>
{children}
</a>
);
}
── -->
<style>
/* ── Default: browser cross-fade ─────────────────────────────────────
Already free — no CSS needed. Customize below to override. */
/* ── Directional slide transition ────────────────────────────────────
Set data-transition-direction="forward"|"backward" on <html> */
[data-transition-direction="forward"] ::view-transition-old(root) {
animation: slide-out-left 350ms cubic-bezier(0.4, 0, 0.2, 1) both;
}
[data-transition-direction="forward"] ::view-transition-new(root) {
animation: slide-in-right 350ms cubic-bezier(0.4, 0, 0.2, 1) both;
}
[data-transition-direction="backward"] ::view-transition-old(root) {
animation: slide-out-right 350ms cubic-bezier(0.4, 0, 0.2, 1) both;
}
[data-transition-direction="backward"] ::view-transition-new(root) {
animation: slide-in-left 350ms cubic-bezier(0.4, 0, 0.2, 1) both;
}
@keyframes slide-out-left { to { transform: translateX(-100%); opacity: 0; } }
@keyframes slide-in-right { from { transform: translateX( 100%); opacity: 0; } }
@keyframes slide-out-right { to { transform: translateX( 100%); opacity: 0; } }
@keyframes slide-in-left { from { transform: translateX(-100%); opacity: 0; } }
/* ── Shared element morph ─────────────────────────────────────────────
Add view-transition-name per item: style={{ viewTransitionName: `card-${id}` }}
Browser auto-morphs size + position between old and new state. */
::view-transition-old(card-hero),
::view-transition-new(card-hero) {
animation-duration: 500ms;
animation-timing-function: cubic-bezier(0.2, 0, 0, 1); /* Material You */
}
/* ── Reduced motion: collapse to near-instant cross-fade ─────────────
Default browser cross-fade is acceptable (no spatial displacement).
Disable custom directional slides which have large displacement. */
@media (prefers-reduced-motion: reduce) {
[data-transition-direction] ::view-transition-old(root),
[data-transition-direction] ::view-transition-new(root) {
animation-duration: 0.01ms !important;
}
}
</style>
Usage Notes
- Baseline Newly Available Oct 2025: Chrome 111+, Edge 111+, Safari 18+, Firefox 133+. ~92% coverage.
- Always feature-detect: if (!document.startViewTransition) — do not assume support.
- view-transition-name must be unique in the document at any moment — duplicates cause visual glitches.
- React 18: use ReactDOM.flushSync inside startViewTransition callback to ensure synchronous DOM commit.
- The default browser cross-fade needs no CSS — only add custom CSS to override the default.
- Cross-document view transitions (MPA): require @view-transition { navigation: auto } in CSS (Chrome 126+, Safari 18.2+).
Accessibility
- Default cross-fade (no spatial displacement) is acceptable under prefers-reduced-motion.
- Directional slides must be collapsed to 0.01ms under reduced motion.
- Shared element morphs should also be collapsed — large position/scale changes are motion-sensitive.
Loaded when retrieval picks the atom as a focal / direct hit.
ViewTransitionsApi [template] v1.0.0
View Transitions API pattern using document.startViewTransition() for SPA route changes — cross-fade default, directional slide override at 350ms cubic-bezier(0.4,0,0.2,1), and shared-element morph via view-transition-name (Baseline Oct 2025: Chrome 111+, Safari 18+, Firefox 133+).
Language
javascript
Body
<!-- ── Feature detection — required (Firefox < 133 has no support) ──── -->
<script>
/**
* Wrap any DOM mutation in startViewTransition for free cross-fade.
* Pass a feature-detected wrapper to keep consumer code identical.
*/
function withViewTransition(updateDom) {
if (!document.startViewTransition) {
updateDom(); // instant fallback
return;
}
return document.startViewTransition(updateDom);
}
// Async variant (fetches data then updates DOM)
async function navigateTo(href) {
const data = await fetch(href).then(r => r.json());
if (!document.startViewTransition) { renderPage(data); return; }
await document.startViewTransition(async () => {
await renderPage(data);
}).finished;
}
</script>
<!-- ── React / Next.js App Router (Client Component) ──────────────────
"use client";
import { useRouter } from "next/navigation";
import ReactDOM from "react-dom";
export function NavLink({ href, children }) {
const router = useRouter();
return (
<a href={href} onClick={(e) => {
e.preventDefault();
if (!document.startViewTransition) { router.push(href); return; }
document.startViewTransition(() => {
ReactDOM.flushSync(() => router.push(href));
});
}}>
{children}
</a>
);
}
── -->
<style>
/* ── Default: browser cross-fade ─────────────────────────────────────
Already free — no CSS needed. Customize below to override. */
/* ── Directional slide transition ────────────────────────────────────
Set data-transition-direction="forward"|"backward" on <html> */
[data-transition-direction="forward"] ::view-transition-old(root) {
animation: slide-out-left 350ms cubic-bezier(0.4, 0, 0.2, 1) both;
}
[data-transition-direction="forward"] ::view-transition-new(root) {
animation: slide-in-right 350ms cubic-bezier(0.4, 0, 0.2, 1) both;
}
[data-transition-direction="backward"] ::view-transition-old(root) {
animation: slide-out-right 350ms cubic-bezier(0.4, 0, 0.2, 1) both;
}
[data-transition-direction="backward"] ::view-transition-new(root) {
animation: slide-in-left 350ms cubic-bezier(0.4, 0, 0.2, 1) both;
}
@keyframes slide-out-left { to { transform: translateX(-100%); opacity: 0; } }
@keyframes slide-in-right { from { transform: translateX( 100%); opacity: 0; } }
@keyframes slide-out-right { to { transform: translateX( 100%); opacity: 0; } }
@keyframes slide-in-left { from { transform: translateX(-100%); opacity: 0; } }
/* ── Shared element morph ─────────────────────────────────────────────
Add view-transition-name per item: style={{ viewTransitionName: `card-${id}` }}
Browser auto-morphs size + position between old and new state. */
::view-transition-old(card-hero),
::view-transition-new(card-hero) {
animation-duration: 500ms;
animation-timing-function: cubic-bezier(0.2, 0, 0, 1); /* Material You */
}
/* ── Reduced motion: collapse to near-instant cross-fade ─────────────
Default browser cross-fade is acceptable (no spatial displacement).
Disable custom directional slides which have large displacement. */
@media (prefers-reduced-motion: reduce) {
[data-transition-direction] ::view-transition-old(root),
[data-transition-direction] ::view-transition-new(root) {
animation-duration: 0.01ms !important;
}
}
</style>
Usage Notes
- Baseline Newly Available Oct 2025: Chrome 111+, Edge 111+, Safari 18+, Firefox 133+. ~92% coverage.
- Always feature-detect: if (!document.startViewTransition) — do not assume support.
- view-transition-name must be unique in the document at any moment — duplicates cause visual glitches.
- React 18: use ReactDOM.flushSync inside startViewTransition callback to ensure synchronous DOM commit.
- The default browser cross-fade needs no CSS — only add custom CSS to override the default.
- Cross-document view transitions (MPA): require @view-transition { navigation: auto } in CSS (Chrome 126+, Safari 18.2+).
Accessibility
- Default cross-fade (no spatial displacement) is acceptable under prefers-reduced-motion.
- Directional slides must be collapsed to 0.01ms under reduced motion.
- Shared element morphs should also be collapsed — large position/scale changes are motion-sensitive.
Source
prime-system/examples/frontend-design/primes/compiled/@community/template-view-transitions-api/atom.yaml