Appearance
@vibe-labs/design-vue-spinners
Vue 3 loading indicators for the Vibe Design System. Spinners, spinner overlays, and skeleton placeholders.
Installation
ts
import { VibeSpinner, VibeSpinnerOverlay, VibeSkeleton } from "@vibe-labs/design-vue-spinners";Requires the CSS layer from @vibe-labs/design-components-spinners.
Philosophy: These components provide base primitives and shape presets for loading states. Most teams will compose their own skeleton screens and loading patterns from these building blocks rather than using them directly — treat them as the foundation, not the finished article.
Components
VibeSpinner
Animated loading spinner with optional label.
Usage
vue
<!-- Basic -->
<VibeSpinner />
<!-- Sizes -->
<VibeSpinner size="xs" />
<VibeSpinner size="sm" />
<VibeSpinner size="md" />
<VibeSpinner size="lg" />
<VibeSpinner size="xl" />
<!-- Colors -->
<VibeSpinner color="accent" />
<VibeSpinner color="muted" />
<VibeSpinner color="white" />
<!-- With label -->
<VibeSpinner label="Loading results…" />
<!-- Stacked label (below spinner) -->
<VibeSpinner label="Please wait" stacked />
<!-- Custom aria-label -->
<VibeSpinner aria-label="Fetching data" />Props
| Prop | Type | Default | Description |
|---|---|---|---|
size | SpinnerSize | "md" | xs · sm · md · lg · xl |
color | SpinnerColor | "accent" | accent · muted · white |
label | string | — | Visible label text |
stacked | boolean | false | Stack label below spinner |
ariaLabel | string | "Loading" | Screen reader label (when no visible label) |
VibeSpinnerOverlay
Full-container overlay with a centred spinner. Fades in/out with <Transition>.
Usage
vue
<!-- Basic (over a parent container) -->
<div style="position: relative">
<DataTable :rows="rows" />
<VibeSpinnerOverlay :visible="isLoading" />
</div>
<!-- With label -->
<VibeSpinnerOverlay :visible="isLoading" label="Saving changes…" />
<!-- Custom size/color -->
<VibeSpinnerOverlay :visible="isLoading" size="xl" color="white" />
<!-- Custom content via slot -->
<VibeSpinnerOverlay :visible="isLoading">
<VibeProgressRing indeterminate />
<p>Processing your request…</p>
</VibeSpinnerOverlay>Props
| Prop | Type | Default | Description |
|---|---|---|---|
visible | boolean | true | Show the overlay |
size | SpinnerSize | "lg" | Spinner size |
color | SpinnerColor | "accent" | Spinner color |
label | string | — | Label text |
stacked | boolean | true | Stack label below spinner |
The parent element should have position: relative for the overlay to fill it correctly.
VibeSkeleton
Placeholder skeleton with shimmer animation for loading states.
Usage
vue
<!-- Single text line -->
<VibeSkeleton />
<!-- Multiple text lines (last line 75% width for natural look) -->
<VibeSkeleton :lines="3" />
<!-- Shapes -->
<VibeSkeleton shape="text" />
<VibeSkeleton shape="circle" />
<VibeSkeleton shape="rect" />
<!-- Sizes -->
<VibeSkeleton shape="rect" size="sm" />
<VibeSkeleton shape="rect" size="md" />
<VibeSkeleton shape="rect" size="lg" />
<!-- Custom dimensions -->
<VibeSkeleton shape="rect" width="200px" height="120px" />
<!-- Static (no shimmer animation) -->
<VibeSkeleton static />
<!-- Card skeleton example -->
<div style="display: flex; gap: 1rem; align-items: center">
<VibeSkeleton shape="circle" size="lg" />
<div style="flex: 1">
<VibeSkeleton width="60%" />
<VibeSkeleton :lines="2" />
</div>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
shape | SkeletonShape | "text" | text · circle · rect |
size | SkeletonSize | — | Height preset |
static | boolean | false | Disable shimmer animation |
width | string | — | Custom CSS width |
height | string | — | Custom CSS height |
lines | number | 1 | Number of text lines (text shape only) |
ariaLabel | string | "Loading" | Accessible label |
Accessibility
All components use role="status" for live region announcements. VibeSpinnerOverlay additionally uses aria-live="polite". Screen-reader-only text is included via .sr-only spans when no visible label is present.
Dependencies
| Package | Purpose |
|---|---|
@vibe-labs/design-components-spinners | CSS tokens + generated styles |
Build
bash
npm run buildBuilt with Vite + vite-plugin-dts. Outputs ES module with TypeScript declarations.
Usage Guide
Setup
ts
import { VibeSpinner, VibeSpinnerOverlay, VibeSkeleton } from "@vibe-labs/design-vue-spinners";
import "@vibe-labs/design-components-spinners";VibeSpinner — Practical Examples
Inline Loading State
vue
<script setup lang="ts">
import { ref } from "vue";
import { VibeSpinner } from "@vibe-labs/design-vue-spinners";
const loading = ref(false);
async function save() {
loading.value = true;
await fetch("/api/save", { method: "POST" });
loading.value = false;
}
</script>
<template>
<button @click="save" :disabled="loading">
<VibeSpinner v-if="loading" size="sm" color="white" />
<span v-else>Save</span>
</button>
</template>Full-Page Loading with Label
vue
<script setup lang="ts">
import { VibeSpinner } from "@vibe-labs/design-vue-spinners";
defineProps<{ visible: boolean }>();
</script>
<template>
<Teleport to="body">
<div v-if="visible" class="page-loading">
<VibeSpinner size="xl" label="Loading application…" stacked />
</div>
</Teleport>
</template>VibeSpinnerOverlay — Practical Examples
Table Loading Overlay
vue
<script setup lang="ts">
import { ref } from "vue";
import { VibeSpinnerOverlay } from "@vibe-labs/design-vue-spinners";
const isLoading = ref(false);
</script>
<template>
<div style="position: relative">
<DataTable :rows="rows" />
<VibeSpinnerOverlay :visible="isLoading" label="Refreshing data…" />
</div>
</template>VibeSkeleton — Practical Examples
Article Card Skeleton
vue
<script setup lang="ts">
import { VibeSkeleton } from "@vibe-labs/design-vue-spinners";
defineProps<{ loading: boolean; article?: { title: string; excerpt: string } }>();
</script>
<template>
<div class="article-card">
<template v-if="loading">
<!-- Thumbnail -->
<VibeSkeleton shape="rect" size="lg" />
<!-- Title -->
<VibeSkeleton width="70%" />
<!-- Body lines -->
<VibeSkeleton :lines="3" />
</template>
<template v-else>
<h2>{{ article?.title }}</h2>
<p>{{ article?.excerpt }}</p>
</template>
</div>
</template>User Profile Skeleton
vue
<template>
<div v-if="loading" style="display: flex; gap: 1rem; align-items: center">
<VibeSkeleton shape="circle" size="lg" />
<div style="flex: 1">
<VibeSkeleton width="40%" />
<VibeSkeleton width="60%" />
</div>
</div>
<UserProfile v-else :user="user" />
</template>Common Patterns
List of Skeleton Rows
vue
<template>
<ul>
<li v-for="i in 5" :key="i" class="list-row">
<VibeSkeleton shape="circle" />
<div style="flex: 1">
<VibeSkeleton width="50%" />
<VibeSkeleton width="80%" />
</div>
</li>
</ul>
</template>Static Skeleton (No Animation in Print/Reduced Motion)
vue
<template>
<VibeSkeleton :static="prefersReducedMotion" shape="rect" size="md" />
</template>Conditional Overlay with Transition
The overlay already includes a built-in <Transition> fade. Simply toggle the visible prop — no extra wrapper needed:
vue
<template>
<div style="position: relative; min-height: 200px">
<ContentArea />
<VibeSpinnerOverlay :visible="isSaving" size="md" label="Saving…" />
</div>
</template>