Patterns Medium
View Transition Theme Mode
Theme/mode switch demo with shared title and CTA continuity.
Open in Lab
MCP
view-transitions theme css js
Targets: JS HTML
Code
:root {
--bg: #070d19;
--panel: #121c31;
--line: #2a3a57;
--text: #edf4ff;
--muted: #bfd0e8;
--accent: #8fe6ff;
--accent-strong: #6fd6ff;
}
html[data-theme="paper"] {
--bg: #f3f0e8;
--panel: #fffdf8;
--line: #d9d1c4;
--text: #22273b;
--muted: #5c6170;
--accent: #305b8f;
--accent-strong: #1e4e86;
}
html[data-theme="noir"] {
--bg: #060607;
--panel: #111214;
--line: #2b2d31;
--text: #f5f7fb;
--muted: #c9ced6;
--accent: #b5d6ff;
--accent-strong: #89b8ff;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
color: var(--text);
font-family: "Avenir Next", "Segoe UI", sans-serif;
background: radial-gradient(
circle at 12% 10%,
color-mix(in srgb, var(--accent) 32%, transparent),
transparent 42%
),
radial-gradient(
circle at 82% 80%,
color-mix(in srgb, var(--accent-strong) 28%, transparent),
transparent 42%
), var(--bg);
transition: background 280ms ease;
}
.topbar {
padding: 0.85rem 1rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.topbar a {
color: var(--accent);
text-decoration: none;
font-weight: 700;
}
.support {
margin: 0;
color: var(--muted);
font-size: 0.86rem;
}
.support.ok {
color: #aff0cc;
}
.support.warn {
color: #ffd6ad;
}
main {
width: min(1080px, 94%);
margin: 0 auto 2rem;
}
.hero {
display: grid;
gap: 0.6rem;
}
.eyebrow {
margin: 0;
text-transform: uppercase;
letter-spacing: 0.1em;
font-size: 0.8rem;
color: var(--accent);
}
h1,
h2,
p {
margin: 0;
}
.desc {
color: var(--muted);
max-width: 66ch;
}
.modes {
margin-top: 0.8rem;
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.mode-btn {
border: 1px solid color-mix(in srgb, var(--line) 72%, white);
border-radius: 999px;
background: color-mix(in srgb, var(--panel) 88%, transparent);
color: var(--text);
padding: 0.35rem 0.7rem;
cursor: pointer;
}
.mode-btn.active {
border-color: color-mix(in srgb, var(--accent) 70%, white);
box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 45%, white) inset;
}
.preview {
margin-top: 1rem;
display: grid;
gap: 1rem;
grid-template-columns: 1.25fr 0.75fr;
}
.panel {
border: 1px solid var(--line);
border-radius: 18px;
background: color-mix(in srgb, var(--panel) 90%, transparent);
padding: 1rem;
display: grid;
gap: 0.7rem;
}
.label {
width: fit-content;
border: 1px solid color-mix(in srgb, var(--line) 75%, white);
border-radius: 999px;
padding: 0.15rem 0.5rem;
font-size: 0.75rem;
color: var(--muted);
}
.title {
font-size: clamp(1.8rem, 4.8vw, 3.2rem);
line-height: 0.95;
}
.muted {
color: var(--muted);
}
.cta {
width: fit-content;
border: 0;
border-radius: 999px;
padding: 0.6rem 0.9rem;
font-weight: 700;
color: #051a26;
background: linear-gradient(130deg, var(--accent), var(--accent-strong));
cursor: pointer;
}
.side {
align-content: center;
gap: 0.75rem;
}
.swatch {
border-radius: 12px;
min-height: 88px;
border: 1px solid var(--line);
}
.swatch.a {
background: linear-gradient(
130deg,
var(--accent),
color-mix(in srgb, var(--accent-strong) 65%, black)
);
}
.swatch.b {
background: linear-gradient(
130deg,
color-mix(in srgb, var(--panel) 20%, var(--accent)),
color-mix(in srgb, var(--accent-strong) 75%, black)
);
}
.swatch.c {
background: linear-gradient(
130deg,
color-mix(in srgb, var(--accent-strong) 80%, black),
color-mix(in srgb, var(--accent) 30%, var(--bg))
);
}
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 360ms;
animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
}
@media (max-width: 880px) {
.preview {
grid-template-columns: 1fr;
}
}if (!window.MotionPreference) {
const __mql = window.matchMedia("(prefers-reduced-motion: reduce)");
const __listeners = new Set();
const MotionPreference = {
prefersReducedMotion() {
return __mql.matches;
},
setOverride(value) {
const reduced = Boolean(value);
document.documentElement.classList.toggle("reduced-motion", reduced);
window.dispatchEvent(new CustomEvent("motion-preference", { detail: { reduced } }));
for (const listener of __listeners) {
try {
listener({ reduced, override: reduced, systemReduced: __mql.matches });
} catch {}
}
},
onChange(listener) {
__listeners.add(listener);
try {
listener({
reduced: __mql.matches,
override: null,
systemReduced: __mql.matches,
});
} catch {}
return () => __listeners.delete(listener);
},
getState() {
return { reduced: __mql.matches, override: null, systemReduced: __mql.matches };
},
};
window.MotionPreference = MotionPreference;
}
const themes = ["neon", "paper", "noir"];
const root = document.documentElement;
const modes = document.getElementById("modes");
const support = document.getElementById("support");
const reduced = window.MotionPreference.prefersReducedMotion();
const state = { theme: root.dataset.theme || "neon" };
function applyTheme(next) {
root.dataset.theme = next;
state.theme = next;
renderButtons();
}
function transition(update) {
if (!reduced && document.startViewTransition) {
document.startViewTransition(update);
} else {
update();
}
}
function renderButtons() {
modes.innerHTML = "";
themes.forEach((theme) => {
const btn = document.createElement("button");
btn.className = `mode-btn ${state.theme === theme ? "active" : ""}`;
btn.textContent = theme;
btn.addEventListener("click", () => {
if (theme === state.theme) return;
transition(() => applyTheme(theme));
});
modes.appendChild(btn);
});
}
function renderSupport() {
if (document.startViewTransition && !reduced) {
support.textContent = "Animated mode switch enabled with shared title/CTA continuity.";
support.classList.add("ok");
} else {
support.textContent = "Fallback mode: theme updates are instant and fully usable.";
support.classList.add("warn");
}
}
renderButtons();
renderSupport();<!doctype html>
<html lang="en" data-theme="neon">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Demo 18 - Theme Mode Transition</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<header class="topbar">
<a href="../">Back to demos</a>
<p id="support" class="support"></p>
</header>
<main>
<section class="hero">
<p class="eyebrow">Demo 18</p>
<h1>Theme/Mode Shared-Element Transition</h1>
<p class="desc">Switch between visual modes while preserving hero identity and CTA hierarchy.</p>
<div class="modes" id="modes"></div>
</section>
<section class="preview">
<article class="panel">
<p class="label">Brand Core</p>
<h2 class="title" style="view-transition-name:brand-title">Nova Motion</h2>
<p class="muted">A shared heading stays continuous while global palette and texture shift.</p>
<button class="cta" style="view-transition-name:brand-cta">Get Early Access</button>
</article>
<article class="panel side">
<div class="swatch a"></div>
<div class="swatch b"></div>
<div class="swatch c"></div>
</article>
</section>
</main>
<script src="script.js"></script>
</body>
</html>View Transition Theme Mode
Theme/mode switch demo with shared title and CTA continuity.
Source
- Repository:
libs-gen - Original demo id:
18-view-transition-theme-mode
Notes
Theme/mode switch demo with shared title and CTA continuity.