Skip to content

UISpinner

UISpinner

A versatile spinner component that provides visual feedback for loading states with customizable size, color, and animation options.

<template>
<UISpinner
:size="'md'"
:color="'primary'"
:speed="'normal'"
/>
</template>

Props

  • size (string): Spinner size (‘sm’ | ‘md’ | ‘lg’)
  • color (string): Spinner color
  • speed (string): Animation speed
  • label (string): Accessibility label
  • overlay (boolean): Show as overlay
  • fullscreen (boolean): Display fullscreen
  • variant (string): Visual variant

Slots

  • default: Content inside spinner
  • label: Custom label content

Usage Examples

  1. Basic Spinner:
<template>
<UISpinner size="md" />
</template>
  1. Loading Button:
<template>
<UIButton
:disabled="loading"
@click="handleClick"
>
<template v-if="loading">
<UISpinner
size="sm"
class="mr-2"
/>
Loading...
</template>
<template v-else>
Submit
</template>
</UIButton>
</template>
<script setup>
const loading = ref(false)
const handleClick = async () => {
loading.value = true
try {
await submitForm()
} finally {
loading.value = false
}
}
</script>
  1. Loading Overlay:
<template>
<div class="relative">
<div class="p-6 border rounded-lg">
<h3 class="text-lg font-medium mb-4">
Content Section
</h3>
<p class="text-gray-600">
Content goes here...
</p>
</div>
<UISpinner
v-if="loading"
:overlay="true"
size="lg"
>
<div class="text-center">
<div class="text-lg font-medium mt-4">
Loading Data
</div>
<div class="text-sm text-gray-500">
Please wait...
</div>
</div>
</UISpinner>
</div>
</template>
<script setup>
const loading = ref(true)
onMounted(async () => {
await loadData()
loading.value = false
})
</script>

Best Practices

  1. Visual Design:

    • Appropriate sizing
    • Clear visibility
    • Consistent styling
    • Proper spacing
  2. User Experience:

    • Clear loading state
    • Appropriate timing
    • Context indication
    • Smooth animation
  3. Accessibility:

    • ARIA labels
    • Loading indicators
    • Screen reader support
    • Focus management
  4. Implementation:

    • Performance optimization
    • State management
    • Error handling
    • Loading timeouts

Common Use Cases

  1. Form Submission:
<template>
<form @submit.prevent="handleSubmit">
<div class="space-y-4">
<UIInput
v-model="formData.name"
:disabled="submitting"
/>
<UIButton
type="submit"
:disabled="submitting"
>
<template v-if="submitting">
<UISpinner
size="sm"
class="mr-2"
/>
Submitting...
</template>
<template v-else>
Submit Form
</template>
</UIButton>
</div>
</form>
</template>
<script setup>
const formData = reactive({
name: ''
})
const submitting = ref(false)
const handleSubmit = async () => {
submitting.value = true
try {
await submitForm(formData)
} finally {
submitting.value = false
}
}
</script>
  1. Data Loading:
<template>
<div class="space-y-4">
<div class="flex justify-between items-center">
<h2 class="text-xl font-medium">
Data Table
</h2>
<UIButton
:disabled="loading"
@click="refreshData"
>
<template v-if="loading">
<UISpinner
size="sm"
class="mr-2"
/>
Refreshing...
</template>
<template v-else>
Refresh
</template>
</UIButton>
</div>
<div class="relative">
<UITable
:data="tableData"
:columns="columns"
/>
<UISpinner
v-if="loading"
:overlay="true"
size="lg"
>
<div class="text-center text-gray-600">
Loading data...
</div>
</UISpinner>
</div>
</div>
</template>
  1. Infinite Scroll:
<template>
<div class="space-y-4">
<div
v-for="item in items"
:key="item.id"
class="p-4 border rounded-lg"
>
{{ item.content }}
</div>
<div
v-if="loading"
class="py-4 text-center"
>
<UISpinner
size="md"
class="mb-2"
/>
<div class="text-sm text-gray-500">
Loading more items...
</div>
</div>
<div
v-if="!hasMore"
class="py-4 text-center text-gray-500"
>
No more items to load
</div>
</div>
</template>
<script setup>
const items = ref([])
const loading = ref(false)
const hasMore = ref(true)
const page = ref(1)
const loadMore = async () => {
if (loading.value || !hasMore.value) return
loading.value = true
try {
const newItems = await fetchItems(page.value)
items.value.push(...newItems)
hasMore.value = newItems.length > 0
page.value++
} finally {
loading.value = false
}
}
onMounted(() => {
loadMore()
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
loadMore()
}
},
{ threshold: 0.5 }
)
// Observe last item
})
</script>

Component Composition

The UISpinner component can be composed with other components to create complex loading states:

<template>
<div class="space-y-6">
<!-- Card Loading -->
<UICard>
<template v-if="loading">
<div class="flex items-center justify-center p-8">
<UISpinner size="lg" />
</div>
</template>
<template v-else>
<!-- Card content -->
</template>
</UICard>
<!-- Button Loading States -->
<div class="flex space-x-4">
<UIButton
v-for="action in actions"
:key="action.id"
:disabled="action.loading"
@click="action.handler"
>
<template v-if="action.loading">
<UISpinner
size="sm"
class="mr-2"
/>
{{ action.loadingText }}
</template>
<template v-else>
{{ action.text }}
</template>
</UIButton>
</div>
<!-- Section Loading -->
<div class="relative min-h-[200px]">
<UISpinner
v-if="sectionLoading"
:overlay="true"
size="lg"
>
<div class="text-center space-y-2">
<div class="text-lg font-medium">
{{ loadingMessage }}
</div>
<div class="text-sm text-gray-500">
{{ loadingDescription }}
</div>
</div>
</UISpinner>
<div v-else>
<!-- Section content -->
</div>
</div>
</div>
</template>