vue-skeletal
v0.1.0
Published
Auto-generate skeleton loading screens from real DOM structure in Vue 3
Maintainers
Readme
vue-skeletal
Auto-generate skeleton loading screens from real DOM structure in Vue 3. No manually crafted skeleton templates — your real component IS the skeleton template.
Features
- Scans real DOM to generate matching skeleton shapes automatically
<Skeletal>component,v-skeletaldirective, anduseSkeletalcomposable- Hint directives for fine-tuning (
v-skeletal-shape,v-skeletal-lines,v-skeletal-ignore, etc.) - 4 built-in animations: shimmer, wave, pulse, none
- Theming via props or CSS custom properties
- Dark mode support
- Blueprint caching for instant re-renders
- Accessible (
role="status",aria-busy, sr-only text) - SSR-safe — all DOM access is guarded
- Tree-shakeable, fully typed
Install
npm install vue-skeletalQuick Start
Plugin (registers everything globally)
import { createApp } from 'vue'
import { VueSkeletalPlugin } from 'vue-skeletal'
const app = createApp(App)
app.use(VueSkeletalPlugin, {
animation: 'shimmer', // 'shimmer' | 'wave' | 'pulse' | 'none'
duration: 1.5,
})Component API
Wrap your content with <Skeletal>. It scans the real DOM on mount, generates a matching skeleton, and shows it while loading.
<script setup>
import { ref } from 'vue'
import { Skeletal } from 'vue-skeletal'
const loading = ref(true)
</script>
<template>
<Skeletal :loading="loading" animation="shimmer">
<div class="card">
<img src="..." class="card-image" />
<h3>Title</h3>
<p>Description text here</p>
</div>
</Skeletal>
</template>Directive API
Use v-skeletal on any element. The directive scans the element's children and overlays a skeleton.
<template>
<div v-skeletal="loading">
<img v-skeletal-shape="'circle'" class="avatar" />
<h2>User Name</h2>
<p v-skeletal-lines="3">Bio paragraph text...</p>
<span v-skeletal-ignore>This element is excluded</span>
</div>
</template>Composable API
useSkeletal manages async loading state for you.
<script setup>
import { useSkeletal, Skeletal } from 'vue-skeletal'
const { data, loading, error, refresh } = useSkeletal(() =>
fetch('/api/items').then(r => r.json())
)
</script>
<template>
<Skeletal :loading="loading">
<ul>
<li v-for="item in data" :key="item.id">{{ item.name }}</li>
</ul>
</Skeletal>
</template>Hint Directives
Fine-tune skeleton shapes with hint directives:
| Directive | Description | Example |
|-----------|-------------|---------|
| v-skeletal-shape | Force a shape: 'rect', 'circle', 'text', 'rounded-rect' | v-skeletal-shape="'circle'" |
| v-skeletal-lines | Number of text lines to render | v-skeletal-lines="3" |
| v-skeletal-ignore | Exclude element from scanning | v-skeletal-ignore |
| v-skeletal-width | Override width (px or %) | v-skeletal-width="'80%'" |
| v-skeletal-repeat | Repeat skeleton element N times | v-skeletal-repeat="5" |
Animations
Four built-in animations, controllable via prop or plugin option:
- shimmer — gradient sweep left-to-right (default)
- wave — gradient with linear easing
- pulse — opacity oscillation 0.4 to 1.0
- none — static color, no animation
Theming
Customize colors with the theme prop or CSS custom properties:
<Skeletal
:loading="loading"
:theme="{ baseColor: '#ddd', highlightColor: '#eee' }"
>
...
</Skeletal>Or set CSS variables directly:
.my-container {
--skeletal-base-color: #ddd;
--skeletal-highlight-color: #eee;
}Dark Mode
Pass dark theme colors via the theme prop:
<Skeletal
:loading="loading"
:theme="{ baseColor: '#2a2a2a', highlightColor: '#3a3a3a' }"
>
...
</Skeletal>Props
<Skeletal> Component
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| loading | boolean | (required) | Show skeleton when true |
| animation | 'shimmer' \| 'wave' \| 'pulse' \| 'none' | 'shimmer' | Animation type |
| theme | Partial<SkeletalTheme> | — | Theme color overrides |
| duration | number | 1.5 | Animation duration in seconds |
| cacheKey | string | — | Cache key for blueprint reuse |
| responsive | boolean | false | Re-scan on resize |
| tag | string | 'div' | Root element tag (used during loading) |
v-skeletal Directive
<!-- Boolean binding -->
<div v-skeletal="isLoading">...</div>
<!-- Object binding with options -->
<div v-skeletal="{ loading: isLoading, animation: 'pulse', duration: 2 }">...</div>Accessibility
Skeleton screens include:
role="status"on the skeleton containeraria-busy="true"while loading- Screen-reader-only "Loading..." text
SSR
All DOM access is guarded. During SSR, the component renders content as-is (no skeleton). On client hydration, the skeleton is generated from the real DOM.
Playground
The interactive playground is deployed at shahadh7.github.io/vue-skeletal.
To run it locally:
git clone https://github.com/shahadh7/vue-skeletal.git
cd vue-skeletal
npm install
npm run devAPI Exports
// Plugin
export { VueSkeletalPlugin } from 'vue-skeletal'
// Component
export { Skeletal } from 'vue-skeletal'
// Directives
export { vSkeletal, vSkeletalShape, vSkeletalLines, vSkeletalIgnore, vSkeletalWidth, vSkeletalRepeat } from 'vue-skeletal'
// Composable
export { useSkeletal } from 'vue-skeletal'
export type { UseSkeletalReturn } from 'vue-skeletal'
// Core (advanced)
export { buildBlueprint, scanDOM, classify, renderToDOM, renderToVNodes, blueprintCache } from 'vue-skeletal'
// Animations
export { injectAnimationStyles } from 'vue-skeletal'
// Theme
export { DEFAULT_THEME, DEFAULT_OPTIONS, mergeOptions, applyThemeVars } from 'vue-skeletal'Contributing
npm install # Install dependencies
npm run dev # Start playground dev server
npm run test # Run tests
npm run build # Build library