Grid to Detail
A copy-paste view transitions component in pure HTML, CSS & vanilla JS. Zero dependencies, framework-agnostic, MIT-licensed.
View TransitionsHTMLCSSJavaScriptany framework
Copy into your project
HTML
<div class="nuda-vt-grid">
<div class="nuda-vt-grid__stage" data-view="grid">
<div class="nuda-vt-grid__grid">
<span class="nuda-vt-grid__thumb"></span>
<span class="nuda-vt-grid__thumb nuda-vt-grid__thumb--hero"></span>
<span class="nuda-vt-grid__thumb"></span>
<span class="nuda-vt-grid__thumb"></span>
</div>
<div class="nuda-vt-grid__hero"></div>
</div>
<button type="button" class="nuda-vt-grid__btn">Open / close</button>
</div>CSS
/* Grid to Detail
A gallery thumbnail expands into a hero. The thumbnail and the hero share one
view-transition-name, so the API morphs the small tile into the large view
(and back) — the classic "open the photo" transition.
Customize: --vt-accent gradient, the group easing */
.nuda-vt-grid__stage { width: 240px; height: 150px; }
.nuda-vt-grid__grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
width: 100%;
height: 100%;
}
.nuda-vt-grid__thumb { border-radius: 8px; background: #27272a; }
.nuda-vt-grid__thumb--hero {
background: linear-gradient(135deg, #e4ff54, #a3e635);
view-transition-name: vt-grid-hero;
}
.nuda-vt-grid__hero {
display: none;
width: 100%;
height: 100%;
border-radius: 12px;
background: linear-gradient(135deg, #e4ff54, #a3e635);
/* Same name as the thumb: only one is in the DOM at a time, so it morphs. */
view-transition-name: vt-grid-hero;
}
.nuda-vt-grid__stage[data-view="detail"] .nuda-vt-grid__grid { display: none; }
.nuda-vt-grid__stage[data-view="detail"] .nuda-vt-grid__hero { display: block; }
.nuda-vt-grid__btn {
background: #e4ff54;
color: #09090b;
border: none;
border-radius: 8px;
padding: 0.5rem 1rem;
font-weight: 700;
cursor: pointer;
}
.nuda-vt-grid__btn:focus-visible {
outline: 2px solid #e4ff54;
outline-offset: 2px;
}
::view-transition-group(vt-grid-hero) {
animation-duration: 0.45s;
animation-timing-function: cubic-bezier(0.34, 1.2, 0.64, 1);
}
@media (prefers-reduced-motion: reduce) {
::view-transition-group(vt-grid-hero) { animation: none; }
}JavaScript
/* Grid to Detail — vanilla JS
Toggles between the grid and the hero detail inside a view transition. */
(function () {
document.querySelectorAll(".nuda-vt-grid").forEach(function (root) {
var stage = root.querySelector(".nuda-vt-grid__stage");
var btn = root.querySelector(".nuda-vt-grid__btn");
function doSwap() {
stage.dataset.view = stage.dataset.view === "grid" ? "detail" : "grid";
}
btn.addEventListener("click", function () {
if (!document.startViewTransition) { doSwap(); return; }
document.startViewTransition(doSwap);
});
});
})();How to use Grid to Detail
Paste the HTML where you need it and the CSS into a global stylesheet (or a <style> tag). Every class is prefixed nuda- so it never collides with Tailwind or your own styles. Tweak the CSS custom properties to match your design system.
Works in React, Vue, Svelte, Astro, Next.js, Nuxt, Laravel Blade, Django, Rails — or a single .html file. No npm install, no build step.