Skip to content

Skeleton Morph

A copy-paste view transitions component in pure HTML, CSS & vanilla JS. Zero dependencies, framework-agnostic, MIT-licensed.

View TransitionsHTMLJavaScriptCSSany framework

This is the loaded headline.

Loading

Copy into your project

HTML
<div class="nuda-vt2-skeleton-morph">
  <div class="nuda-vt2-skeleton-morph__card" data-state="loading">
    <span class="nuda-vt2-skeleton-morph__bar nuda-vt2-skeleton-morph__bar--wide"></span>
    <span class="nuda-vt2-skeleton-morph__bar"></span>
    <p class="nuda-vt2-skeleton-morph__text">This is the loaded headline.</p>
  </div>
  <button type="button" class="nuda-vt2-skeleton-morph__btn">Load content</button>
  <p class="nuda-vt2-skeleton-morph__status" aria-live="polite">Loading</p>
</div>
JavaScript
/* Skeleton Morph — vanilla JS
   Crossfades a shimmering skeleton into its loaded content inside a short
   view transition, and announces the state change for screen readers. */

(function () {
  document.querySelectorAll(".nuda-vt2-skeleton-morph").forEach(function (root) {
    var card = root.querySelector(".nuda-vt2-skeleton-morph__card");
    var status = root.querySelector(".nuda-vt2-skeleton-morph__status");
    var btn = root.querySelector(".nuda-vt2-skeleton-morph__btn");

    function doSwap() {
      var next = card.dataset.state === "loading" ? "loaded" : "loading";
      card.dataset.state = next;
      status.textContent = next === "loaded" ? "Content loaded" : "Loading";
    }

    btn.addEventListener("click", function () {
      if (!document.startViewTransition) { doSwap(); return; }
      document.startViewTransition(doSwap);
    });
  });
})();
CSS
.nuda-vt2-skeleton-morph-demo {
  padding: .5rem;
}

.nuda-vt2-skeleton-morph {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}

.nuda-vt2-skeleton-morph__card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: 150px;
  min-height: 56px;
  padding: 10px;
  border-radius: 10px;
  border: 1px solid rgba(255,255,255,.1);
  background: #18181b;
  view-transition-name: vt2-skeleton-card;
}

.nuda-vt2-skeleton-morph__bar {
  display: none;
  height: 10px;
  border-radius: 5px;
  width: 60%;
  background: linear-gradient(90deg,#27272a 25%,#3f3f46 37%,#27272a 63%);
  background-size: 400% 100%;
  animation: _nuda-vt2shimmer 1.4s ease-in-out infinite;
}

.nuda-vt2-skeleton-morph__bar--wide {
  width: 90%;
}

.nuda-vt2-skeleton-morph__card[data-state="loading"] .nuda-vt2-skeleton-morph__bar {
  display: block;
}

.nuda-vt2-skeleton-morph__text {
  display: none;
  margin: 0;
  font-size: .74rem;
  font-weight: 700;
  color: #e4ff54;
}

.nuda-vt2-skeleton-morph__card[data-state="loaded"] .nuda-vt2-skeleton-morph__text {
  display: block;
}

.nuda-vt2-skeleton-morph__btn {
  background: #e4ff54;
  color: #09090b;
  border: none;
  border-radius: 8px;
  padding: .4rem .8rem;
  font-size: .75rem;
  font-weight: 700;
  cursor: pointer;
}

.nuda-vt2-skeleton-morph__btn:focus-visible {
  outline: 2px solid #e4ff54;
  outline-offset: 2px;
}

.nuda-vt2-skeleton-morph__status {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

@keyframes _nuda-vt2shimmer {
  0% {
    background-position: 100% 0;
  }
  100% {
    background-position: 0 0;
  }
}

::view-transition-old(vt2-skeleton-card),::view-transition-new(vt2-skeleton-card) {
  animation-duration: .25s;
  animation-timing-function: ease-out;
}

@media (prefers-reduced-motion:reduce) {
  .nuda-vt2-skeleton-morph__bar {
    animation: none;
    background-position: 0 0;
  }
  ::view-transition-old(vt2-skeleton-card),::view-transition-new(vt2-skeleton-card) {
    animation: none;
  }
}

How to use Skeleton Morph

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