Directional Nav
A copy-paste view transitions component in pure HTML, CSS & vanilla JS. Zero dependencies, framework-agnostic, MIT-licensed.
View TransitionsHTMLJavaScriptCSSany framework
Page 1
Copy into your project
HTML
<div class="nuda-vt2-directional-nav">
<div class="nuda-vt2-directional-nav__stage" data-page="1">
<span class="nuda-vt2-directional-nav__label">Page 1</span>
</div>
<div class="nuda-vt2-directional-nav__controls">
<button type="button" class="nuda-vt2-directional-nav__btn" aria-label="Go back">←</button>
<button type="button" class="nuda-vt2-directional-nav__btn" aria-label="Go forward">→</button>
</div>
</div>JavaScript
/* Directional Nav — vanilla JS
Sets a --vt2-dir-nav custom property (1 or -1) before starting the
transition; the keyframes read it via calc(), so a single pair of
animations serves both forward and back navigation correctly. */
(function () {
document.querySelectorAll(".nuda-vt2-directional-nav").forEach(function (root) {
var stage = root.querySelector(".nuda-vt2-directional-nav__stage");
var label = root.querySelector(".nuda-vt2-directional-nav__label");
var buttons = root.querySelectorAll(".nuda-vt2-directional-nav__btn");
function go(delta) {
document.documentElement.style.setProperty("--vt2-dir-nav", String(delta > 0 ? 1 : -1));
function doSwap() {
var page = Math.min(3, Math.max(1, Number(stage.dataset.page) + delta));
stage.dataset.page = String(page);
label.textContent = "Page " + page;
}
if (!document.startViewTransition) { doSwap(); return; }
document.startViewTransition(doSwap);
}
buttons[0].addEventListener("click", function () { go(-1); });
buttons[1].addEventListener("click", function () { go(1); });
});
})();CSS
.nuda-vt2-directional-nav-demo {
padding: .5rem;
}
.nuda-vt2-directional-nav {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.nuda-vt2-directional-nav__stage {
display: flex;
align-items: center;
justify-content: center;
width: 150px;
height: 72px;
border-radius: 12px;
border: 1px solid rgba(255,255,255,.1);
overflow: hidden;
background: #18181b;
view-transition-name: vt2-directional-nav-stage;
}
.nuda-vt2-directional-nav__label {
font-size: .78rem;
font-weight: 700;
color: #e4ff54;
}
.nuda-vt2-directional-nav__controls {
display: flex;
gap: 6px;
}
.nuda-vt2-directional-nav__btn {
min-width: 44px;
min-height: 44px;
background: #e4ff54;
color: #09090b;
border: none;
border-radius: 8px;
padding: .3rem .6rem;
font-size: .85rem;
font-weight: 700;
cursor: pointer;
}
.nuda-vt2-directional-nav__btn:focus-visible {
outline: 2px solid #e4ff54;
outline-offset: 2px;
}
@keyframes _nuda-vt2navout {
from {
transform: translateX(0);
}
to {
transform: translateX(calc(var(--vt2-dir-nav,1) * -100%));
}
}
@keyframes _nuda-vt2navin {
from {
transform: translateX(calc(var(--vt2-dir-nav,1) * 100%));
}
to {
transform: translateX(0);
}
}
::view-transition-old(vt2-directional-nav-stage) {
animation: _nuda-vt2navout .4s cubic-bezier(.4,0,.2,1) both;
}
::view-transition-new(vt2-directional-nav-stage) {
animation: _nuda-vt2navin .4s cubic-bezier(.4,0,.2,1) both;
}
@media (prefers-reduced-motion:reduce) {
::view-transition-old(vt2-directional-nav-stage),::view-transition-new(vt2-directional-nav-stage) {
animation: none;
}
}
How to use Directional Nav
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.