auto-skeleton
auto-skeleton-vue

Auto-generated skeleton loaders that mirror a component's real rendered layout. Zero authoring, always in sync.

auto-skeleton-vue

npm versionNuxt 3 & 4license

Auto-generated skeleton loaders for Vue 3 and Nuxt that mirror a component's real rendered layout — no separate skeleton component to author, and it stays in sync automatically because it's derived from the component itself.

New in 1.0 — Nuxt support. Add modules: ['auto-skeleton-vue/nuxt'] and <AutoSkeleton> is auto-imported, styled, and SSR-safe. See the Nuxt guide and the changelog.

<AutoSkeleton :loading="isLoading">
  <UserCard :user="user" />
</AutoSkeleton>

That's it. No <Skeleton> tags to place inside UserCard, no hand-drawn placeholder to keep matching your layout.

Live demo

Docs & live demo →  ·  interactive demo

Open in StackBlitz

Runnable examples, both open in StackBlitz straight from this repo: Vue · Nuxt.

Toggle loading to watch each component's skeleton generate itself.

How it works

The skeleton is always one render behind:

  1. When loading is false, the real component renders. After paint, AutoSkeleton walks its DOM and captures the layout into a compact, serializable descriptor (text → per-line bars, images → blocks, etc.), keyed by component + viewport width.
  2. When loading is true, it replays the cached descriptor — a pixel-accurate skeleton of the last real render.
  3. On the very first load (no capture yet) it shows a small generic fallback. Enable persist to cache across reloads and skip that flash.

Because every successful render overwrites the cache, changing your component automatically updates its skeleton. There is no sync step, and the child is never force-rendered without data.

Install

npm i auto-skeleton-vue
import { createApp } from 'vue'
import { createAutoSkeleton } from 'auto-skeleton-vue'
import 'auto-skeleton-vue/style.css'

createApp(App)
  .use(
    createAutoSkeleton({
      baseColor: '#e2e5e9',
      highlightColor: '#f2f4f7',
      duration: '1.5s',
      animation: true, // shimmer on by default; set false for static gray
      persist: true, // cache geometry in localStorage across reloads
      version: '1.4.0', // namespace the cache; a new value starts clean
      maxAge: 7 * 24 * 60 * 60 * 1000, // expire persisted geometry after 7 days
      maxEntries: 200, // LRU cap on persisted entries
    }),
  )
  .mount('#app')

The shimmer is disabled automatically when the user has prefers-reduced-motion set. Persisted geometry expires by maxAge, is namespaced by version, and is LRU-capped by maxEntries; call store.clear() to wipe it (e.g. on logout).

You can also import AutoSkeleton directly without installing the plugin; it falls back to sensible defaults.

Nuxt

Add the module — components, composables, and the stylesheet are wired up for you, and rendering is SSR-safe:

npx nuxi module add auto-skeleton-vue

or add it to nuxt.config yourself:

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['auto-skeleton-vue/nuxt'],
  autoSkeleton: { persist: true }, // same options as createAutoSkeleton()
})
<script setup lang="ts">
const { data: user, status } = useLazyFetch('/api/user')
</script>

<template>
  <AutoSkeleton :loading="status === 'pending'">
    <UserCard :user="user" />
  </AutoSkeleton>
</template>

See the Nuxt guide for SSR details, client-only fetches, and custom stores, or try the Nuxt example in your browser:

Open in StackBlitz

<AutoSkeleton> props

PropTypeDefaultDescription
loadingboolean—Show the skeleton when true, the content when false.
idstringautoCache identity. Defaults to the child's name / file.
countnumber1Repeat the captured block N times (for lists).
animatedbooleanglobalPlay the shimmer; overrides the global default when set.
baseColorstring—Per-instance theme override.
highlightColorstring—Per-instance theme override.
radiusstring—Border radius for media/box primitives.
durationstring—Shimmer animation duration.

Limitations

  • First-ever load shows a generic fallback (mitigated by persist).
  • Responsive reflow is handled by width-bucketed cache keys + resize re-capture, not by live reflow of a captured descriptor.
  • Capture is client-only. Server rendering works (and hydrates cleanly), but a loading state rendered on the server shows the fallback until the page mounts.
  • Pseudo-elements, CSS transforms, and position: sticky are approximated.

License

MIT