Browser-native animated navigation between views.
morph · 500ms · approximates the native View Transitions API with a CSS crossfade
<div class="mot-vt-stage" data-style="morph">
<div class="mot-vt-view mot-vt-view--a mot-vt-view--active" style="background:#D9C8F0;">List view</div>
<div class="mot-vt-view mot-vt-view--b">Detail view</div>
<p class="mot-vt-caption">morph · 500ms · approximates the native View Transitions API with a CSS crossfade</p>
</div>
<style>
.mot-vt-stage { position: relative; width: 100%; max-width: 320px; height: 90px; background: #FAF7F2; border: 1px solid #24143022; border-radius: 12px; overflow: hidden; font: 12px system-ui, sans-serif; color: #241430aa; }
.mot-vt-view { position: absolute; inset: 0; display: grid; place-items: center; font: 700 14px system-ui, sans-serif; color: #241430; opacity: 0; }
.mot-vt-view--b { background: #fff; border: 2px dashed #24143033; }
.mot-vt-view--active { opacity: 1; }
.mot-vt-caption { position: absolute; bottom: -24px; left: 0; }
@media (prefers-reduced-motion: no-preference) {
[data-style="morph"] .mot-vt-view--active { animation: mot-vt-morph 500ms ease-out; }
[data-style="fade"] .mot-vt-view--active { animation: mot-vt-fade 500ms ease-out; }
[data-style="slide"] .mot-vt-view--active { animation: mot-vt-slide 500ms ease-out; }
@keyframes mot-vt-morph { from { opacity: 0; transform: scale(.9) rotate(-2deg); } to { opacity: 1; transform: scale(1) rotate(0); } }
@keyframes mot-vt-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes mot-vt-slide { from { opacity: 0; transform: translateX(24px); } to { opacity: 1; transform: translateX(0); } }
}
</style><div class="relative w-full max-w-[320px] h-[90px] bg-[#FAF7F2] border border-[#24143022] rounded-xl overflow-hidden font-sans text-xs text-[#241430]/70" data-style="morph">
<div class="tw-vt-a tw-vt-active absolute inset-0 grid place-items-center font-bold text-sm text-[#241430] opacity-0" style="background:#D9C8F0;">List view</div>
<div class="tw-vt-b absolute inset-0 grid place-items-center font-bold text-sm text-[#241430] opacity-0 bg-white border-2 border-dashed border-[#24143033]">Detail view</div>
<p class="absolute -bottom-6 left-0">morph · 500ms · approximates the native View Transitions API with a CSS crossfade</p>
</div>
<style>
.tw-vt-active { opacity: 1 !important; }
@media (prefers-reduced-motion: no-preference) {
[data-style="morph"] .tw-vt-active { animation: mot-vt-tw-morph 500ms ease-out; }
[data-style="fade"] .tw-vt-active { animation: mot-vt-tw-fade 500ms ease-out; }
[data-style="slide"] .tw-vt-active { animation: mot-vt-tw-slide 500ms ease-out; }
@keyframes mot-vt-tw-morph { from { opacity: 0; transform: scale(.9) rotate(-2deg); } to { opacity: 1; transform: scale(1) rotate(0); } }
@keyframes mot-vt-tw-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes mot-vt-tw-slide { from { opacity: 0; transform: translateX(24px); } to { opacity: 1; transform: translateX(0); } }
}
</style>export function ViewTransition({
view = false,
style = "morph",
duration = 500,
accent = "#D9C8F0",
}) {
const anim = { morph: "mot-vt-morph", fade: "mot-vt-fade", slide: "mot-vt-slide" }[style];
return (
<div style={{ position: "relative", width: "100%", maxWidth: 320, height: 90, background: "#FAF7F2", border: "1px solid #24143022", borderRadius: 12, overflow: "hidden", fontFamily: "system-ui, sans-serif", fontSize: 12, color: "#241430aa" }}>
<div className={!view ? "view active" : "view"} style={{ position: "absolute", inset: 0, display: "grid", placeItems: "center", fontWeight: 700, fontSize: 14, color: "#241430", background: accent, opacity: view ? 0 : undefined }}>List view</div>
<div className={view ? "view active" : "view"} style={{ position: "absolute", inset: 0, display: "grid", placeItems: "center", fontWeight: 700, fontSize: 14, color: "#241430", background: "#fff", border: "2px dashed #24143033", opacity: view ? undefined : 0 }}>Detail view</div>
<p style={{ position: "absolute", bottom: -24, left: 0 }}>{style} · {duration}ms · approximates the native View Transitions API with a CSS crossfade</p>
<style>{`
@media (prefers-reduced-motion: no-preference) {
.view.active { animation: ${anim} ${duration}ms ease-out; }
@keyframes mot-vt-morph { from { opacity: 0; transform: scale(.9) rotate(-2deg); } to { opacity: 1; transform: scale(1) rotate(0); } }
@keyframes mot-vt-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes mot-vt-slide { from { opacity: 0; transform: translateX(24px); } to { opacity: 1; transform: translateX(0); } }
}
`}</style>
</div>
);
}