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 colorspeed(string): Animation speedlabel(string): Accessibility labeloverlay(boolean): Show as overlayfullscreen(boolean): Display fullscreenvariant(string): Visual variant
Slots
default: Content inside spinnerlabel: Custom label content
Usage Examples
- Basic Spinner:
<template> <UISpinner size="md" /></template>- 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>- 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
-
Visual Design:
- Appropriate sizing
- Clear visibility
- Consistent styling
- Proper spacing
-
User Experience:
- Clear loading state
- Appropriate timing
- Context indication
- Smooth animation
-
Accessibility:
- ARIA labels
- Loading indicators
- Screen reader support
- Focus management
-
Implementation:
- Performance optimization
- State management
- Error handling
- Loading timeouts
Common Use Cases
- 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>- 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>- 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>