Advanced Usage

Scopes, per-container control, scoped themes and custom animations.

Advanced Usage

Scopes

<Skeletonizer> can be nested freely. Each instance can override the global state and the animation for just its subtree:

<Skeletonizer :enabled="globalLoading">
  <Header />

  <!-- This card uses its own loading flag and a different animation -->
  <Skeletonizer :enabled="cardLoading" animation="pulse">
    <PromoCard />
  </Skeletonizer>
</Skeletonizer>

A scope's enabled prop always wins over the global flag. Omit it to follow the global state.

Per-container control via the composable

Because control is centralised in a reactive store, you can drive skeletons from anywhere — a toolbar, a route guard, a Pinia action:

const { enable, disable, refresh } = useSkeletonizer()

watch(() => route.params.id, async () => {
  enable()
  await loadData()
  disable()
})

Call refresh() after the DOM changes outside Nuxt's reactivity (e.g. a third-party widget mounted new nodes) to re-scan all hosts.

Scoped themes

Apply a theme to a single subtree without touching the rest of the page:

<Skeletonizer :enabled="pending" :theme="{ baseColor: '#fde68a', highlightColor: '#fffbeb' }">
  <PromoBanner />
</Skeletonizer>

Custom animations

Register your own animation at runtime and switch to it:

const { registerAnimation, setAnimation } = useSkeletonizer()

registerAnimation({
  name: 'blink',
  css: `
    @keyframes sk-blink { 0%,100% { opacity: 1 } 50% { opacity: .3 } }
    .sk-anim-blink .sk-bone { animation: sk-blink var(--sk-duration) infinite }
  `,
})

setAnimation('blink')

See Theming for the full animation reference.

Steering the engine

Use the directives to fine-tune what gets skeletonized:

<Skeletonizer :enabled="pending">
  <Logo v-skeleton-keep />                 <!-- always real -->
  <span v-skeleton-replace="'badge'">{{ status }}</span>
  <li v-for="i in items" v-skeleton-union>{{ i.label }}</li>
</Skeletonizer>