Skip to content

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.

More view transitions components

← Browse all NudaUI components