UIPackage
Menu

Framework

Change language

Boilerplate repo

Skeleton

skeletonui

Animated placeholder rectangles for loading states — drop one in shape of the content that’s about to render. Variants for text lines, avatars, rounded rectangles, and circles.

Also available for React ->

Installation

$npx shadcn-vue@latest add https://uipkge.dev/r/vue/skeleton.json
Named registry:npx shadcn-vue@latest add @uipkge/skeletonInstalls to:app/components/ui/skeleton/

Examples

Loading interactive previews…

Props

NameType / ValuesDefaultRequired
classHTMLAttributes['class']optional
variant
'rectangular''rounded''circular''text''avatar''image''card''table-row'
'rectangular'optional
widthstringoptional
heightstringoptional
loadingbooleantrueoptional

npm dependencies

Used by

Files installed (6)

  • app/components/ui/skeleton/Skeleton.vue1.9 kB
    <script setup lang="ts">
    import { computed } from 'vue'
    import type { HTMLAttributes } from 'vue'
    import { cn } from '@/lib/utils'
    
    interface Props {
      class?: HTMLAttributes['class']
      variant?: 'rectangular' | 'rounded' | 'circular' | 'text' | 'avatar' | 'image' | 'card' | 'table-row'
      width?: string
      height?: string
      loading?: boolean
    }
    
    const props = withDefaults(defineProps<Props>(), {
      variant: 'rectangular',
      loading: true,
    })
    
    const variantClasses = {
      rectangular: '',
      rounded: 'rounded-md',
      circular: 'rounded-full',
      text: 'rounded h-4 w-full',
      avatar: 'rounded-full size-10',
      image: 'rounded-lg size-24',
      card: 'rounded-xl size-full min-h-30',
      'table-row': 'rounded h-10 w-full',
    }
    
    const variantStyles = computed(() => {
      const base: Record<string, string> = {}
      if (props.width) base.width = props.width
      if (props.height) base.height = props.height
      return Object.keys(base).length > 0 ? base : undefined
    })
    </script>
    
    <template>
      <div
        v-if="loading"
        data-uipkge
        data-slot="skeleton"
        aria-hidden="true"
        :class="cn('skeleton-shimmer', variantClasses[variant || 'rectangular'], props.class)"
        :style="variantStyles"
      />
      <slot v-else />
    </template>
    
    <style scoped>
    /* Slow left-to-right gradient sweep. Per NN/g, slow shimmer reads as
       faster loading than a pulse. 1.8s loop is the documented sweet spot. */
    .skeleton-shimmer {
      background: linear-gradient(
        90deg,
        color-mix(in srgb, var(--muted) 100%, transparent) 0%,
        color-mix(in srgb, var(--muted) 60%, var(--foreground) 8%) 50%,
        color-mix(in srgb, var(--muted) 100%, transparent) 100%
      );
      background-size: 200% 100%;
      animation: skeleton-shimmer 1.8s linear infinite;
    }
    @keyframes skeleton-shimmer {
      from {
        background-position: 200% 0;
      }
      to {
        background-position: -200% 0;
      }
    }
    @media (prefers-reduced-motion: reduce) {
      .skeleton-shimmer {
        animation: none;
        background: var(--muted);
      }
    }
    </style>
    
  • app/components/ui/skeleton/SkeletonGroup.vue0.4 kB
  • app/components/ui/skeleton/SkeletonLoader.vue5.1 kB
  • app/components/ui/skeleton/SkeletonText.vue1.5 kB
  • app/components/ui/skeleton/skeleton.variants.ts1.6 kB
  • app/components/ui/skeleton/index.ts0.5 kB

Raw manifest:https://uipkge.dev/r/vue/skeleton.json