Velocity UI
Loading…
Menu

Skeleton Base

A high-performance, iridescent skeleton loader for building premium loading states. Features customizable shimmer effects, shapes, and sizes.

Installation

Add this component to your project using the CLI:

terminal
npx vui-registry-cli-v1 add skeleton

Source Code

skeleton.tsx
'use client'

import React from 'react'
import { motion } from 'framer-motion'
import { cn } from '@/lib/utils'

export interface SkeletonProps extends React.HTMLAttributes<HTMLDivElement> {
  className?: string
  width?: string | number
  height?: string | number
  circle?: boolean
  show?: boolean
}

/**
 * Premium Skeleton Component
 * Features an iridescent shimmer effect moving diagonally.
 */
export function Skeleton({
  className,
  width,
  height,
  circle,
  show = true,
  ...props
}: SkeletonProps) {
  if (!show) return null

  return (
    <div
      className={cn(
        'relative overflow-hidden bg-foreground/5',
        circle ? 'rounded-full' : 'rounded-md',
        className
      )}
      style={{
        width,
        height,
      }}
      {...props}
    >
      <motion.div
        className="absolute inset-0 -translate-x-full"
        animate={{ translateX: ['-100%', '100%'] }}
        transition={{
          repeat: Infinity,
          duration: 1.5,
          ease: 'linear',
        }}
        style={{
          background: `linear-gradient(
            90deg,
            transparent 0%,
            rgba(var(--foreground-rgb), 0.04) 25%,
            rgba(var(--foreground-rgb), 0.08) 50%,
            rgba(var(--foreground-rgb), 0.04) 75%,
            transparent 100%
          )`,
        }}
      />
      {/* Iridescent shimmer layer for premium feel */}
      <motion.div
        className="absolute inset-0 -translate-x-full opacity-30"
        animate={{ translateX: ['-100%', '100%'] }}
        transition={{
          repeat: Infinity,
          duration: 1.5,
          ease: 'linear',
          delay: 0.1, // Slight offset
        }}
        style={{
          background: `linear-gradient(
            90deg,
            transparent 0%,
            rgba(56, 189, 248, 0.1) 40%, 
            rgba(232, 121, 249, 0.1) 50%,
            transparent 100%
          )`,
        }}
      />
    </div>
  )
}

Source Code

skeleton-line.tsx
'use client'

import React from 'react'
import { Skeleton } from './skeleton'

// Variant 11: Skeleton Line (merged from standalone)
export function SkeletonLine() {
  return (
    <div className="flex flex-col gap-4 p-6 bg-white dark:bg-neutral-900 rounded-xl w-full max-w-sm border border-neutral-100 dark:border-neutral-800 overflow-hidden relative">
      <div className="flex items-center gap-4">
         <Skeleton className="w-12 h-12 rounded-full" circle />
         <div className="flex flex-col gap-2 flex-1">
            <Skeleton className="h-4 w-3/4 rounded" />
            <Skeleton className="h-3 w-1/2 rounded" />
         </div>
      </div>
      <Skeleton className="h-24 w-full rounded-lg" />
    </div>
  )
}

Source Code

skeleton-card.tsx
'use client'

import React from 'react'
import { Skeleton } from './skeleton'

// Variant 1: Standard Card Skeleton
export function SkeletonCard() {
  return (
    <div className="w-full max-w-sm p-4 border rounded-xl space-y-3 bg-background/50 backdrop-blur-sm">
      <Skeleton className="h-[125px] w-full rounded-lg" />
      <div className="space-y-2">
        <Skeleton className="h-4 w-[250px]" />
        <Skeleton className="h-4 w-[200px]" />
      </div>
    </div>
  )
}

Source Code

skeleton-profile.tsx
'use client'

import React from 'react'
import { Skeleton } from './skeleton'

// Variant 2: Profile Skeleton
export function SkeletonProfile() {
  return (
    <div className="flex items-center space-x-4 p-4 border rounded-xl w-full max-w-sm bg-background/50 backdrop-blur-sm">
      <Skeleton className="h-12 w-12 rounded-full" circle />
      <div className="space-y-2">
        <Skeleton className="h-4 w-[150px]" />
        <Skeleton className="h-3 w-[100px]" />
      </div>
    </div>
  )
}

Source Code

skeleton-list.tsx
'use client'

import React from 'react'
import { Skeleton } from './skeleton'

// Variant 3: List/Row Skeleton
export function SkeletonList() {
  return (
    <div className="w-full max-w-sm space-y-3 p-4 border rounded-xl bg-background/50 backdrop-blur-sm">
      {[1, 2, 3].map((i) => (
        <div key={i} className="flex items-center justify-between">
          <div className="flex items-center space-x-3">
            <Skeleton className="h-8 w-8 rounded-full" circle />
            <Skeleton className="h-3 w-[100px]" />
          </div>
          <Skeleton className="h-3 w-[40px]" />
        </div>
      ))}
    </div>
  )
}

Source Code

skeleton-dashboard.tsx
'use client'

import React from 'react'
import { Skeleton } from './skeleton'

// Variant 4: Dashboard Metrics Skeleton
export function SkeletonDashboard() {
  return (
    <div className="grid grid-cols-2 gap-3 w-full max-w-sm">
      {[1, 2].map((i) => (
        <div key={i} className="p-4 border rounded-xl bg-background/50 backdrop-blur-sm space-y-2">
          <Skeleton className="h-3 w-[60px]" />
          <Skeleton className="h-8 w-[80px]" />
          <Skeleton className="h-2 w-[40px] mt-2" />
        </div>
      ))}
    </div>
  )
}

Source Code

skeleton-article.tsx
'use client'

import React from 'react'
import { Skeleton } from './skeleton'

// Variant 5: Article/Content Skeleton
export function SkeletonArticle() {
  return (
    <div className="w-full max-w-sm p-4 border rounded-xl space-y-4 bg-background/50 backdrop-blur-sm">
      <div className="flex items-center space-x-2 mb-4">
        <Skeleton className="h-6 w-6 rounded-full" circle />
        <Skeleton className="h-3 w-[80px]" />
      </div>
      <Skeleton className="h-4 w-full" />
      <Skeleton className="h-4 w-[90%]" />
      <Skeleton className="h-4 w-[95%]" />
      <Skeleton className="h-4 w-[60%]" />
    </div>
  )
}

Source Code

skeleton-chat.tsx
 'use client'
 
 import React from 'react'
 import { Skeleton } from './skeleton'
 
 export function SkeletonChat() {
   return (
     <div className="w-full max-w-md p-4 border rounded-xl bg-background/50 backdrop-blur-sm space-y-3">
       {[...Array(5)].map((_, i) => (
         <div
           key={i}
           className={`flex items-start gap-2 ${i % 2 === 0 ? 'justify-start' : 'justify-end flex-row-reverse'}`}
         >
           <Skeleton className="h-8 w-8 rounded-full" circle />
           <div className="space-y-2 w-2/3">
             <Skeleton className="h-3 w-full" />
             <Skeleton className="h-3 w-1/2" />
           </div>
         </div>
       ))}
     </div>
   )
 }

Source Code

skeleton-grid.tsx
 'use client'
 
 import React from 'react'
 import { Skeleton } from './skeleton'
 
 export function SkeletonGrid() {
   return (
     <div className="w-full max-w-md p-4 border rounded-xl bg-background/50 backdrop-blur-sm">
       <div className="grid grid-cols-3 gap-3">
         {[...Array(9)].map((_, i) => (
           <div key={i} className="space-y-2">
             <Skeleton className="h-24 w-full rounded-lg" />
             <Skeleton className="h-3 w-3/4" />
           </div>
         ))}
       </div>
     </div>
   )
 }

Source Code

skeleton-hero.tsx
 'use client'
 
 import React from 'react'
 import { Skeleton } from './skeleton'
 
 export function SkeletonHero() {
   return (
     <div className="w-full max-w-xl p-4 border rounded-xl bg-background/50 backdrop-blur-sm space-y-4">
       <Skeleton className="h-40 w-full rounded-xl" />
       <div className="space-y-2">
         <Skeleton className="h-5 w-3/4" />
         <Skeleton className="h-4 w-2/3" />
       </div>
       <div className="flex gap-2">
         <Skeleton className="h-10 w-24 rounded-lg" />
         <Skeleton className="h-10 w-24 rounded-lg" />
       </div>
     </div>
   )
 }

Source Code

skeleton-preview.tsx
'use client'

import React, { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { SkeletonCard } from './skeleton-card'
import { SkeletonProfile } from './skeleton-profile'
import { SkeletonList } from './skeleton-list'
import { SkeletonDashboard } from './skeleton-dashboard'
import { SkeletonArticle } from './skeleton-article'
import { SkeletonTable } from './skeleton-table'
import { SkeletonGrid } from './skeleton-grid'
import { SkeletonChat } from './skeleton-chat'
import { SkeletonHero } from './skeleton-hero'
 import { SkeletonSidebar } from './skeleton-sidebar'
 import { SkeletonLine } from './skeleton-line'
 
 // Main Export for Preview Page
 export function SkeletonPreview() {
   const [copied, setCopied] = useState<string | null>(null)
   const copy = (name: string, cmd: string) => {
     navigator.clipboard.writeText(cmd)
     setCopied(name)
     setTimeout(() => setCopied(null), 1200)
   }
   const cmds: Record<string, string> = {
     Card: 'npx vui-registry-cli-v1 add skeleton-card',
     Profile: 'npx vui-registry-cli-v1 add skeleton-profile',
     Dashboard: 'npx vui-registry-cli-v1 add skeleton-dashboard',
     List: 'npx vui-registry-cli-v1 add skeleton-list',
     Article: 'npx vui-registry-cli-v1 add skeleton-article',
     Table: 'npx vui-registry-cli-v1 add skeleton-table',
     Grid: 'npx vui-registry-cli-v1 add skeleton-grid',
     Chat: 'npx vui-registry-cli-v1 add skeleton-chat',
     Hero: 'npx vui-registry-cli-v1 add skeleton-hero',
     Sidebar: 'npx vui-registry-cli-v1 add skeleton-sidebar',
     Line: 'npx vui-registry-cli-v1 add skeleton-line',
   }
 
   return (
     <div className="grid grid-cols-1 md:grid-cols-2 gap-4 w-full max-w-5xl p-4">
       <div className="space-y-4">
         <div className="space-y-1 rounded-2xl p-[2px] border border-border">
           <div className="rounded-xl border border-border bg-background/60 relative px-3 py-2">
             <div className="flex items-center justify-between">
             <p className="text-xs text-muted-foreground uppercase tracking-wider">Line</p>
             <button
               className="text-[10px] font-mono border-b border-border"
               onClick={() => copy('Line', cmds.Line)}
               title="Copy CLI"
             >
               {copied === 'Line' ? <span className="text-emerald-500">Copied</span> : 'CLI'}
             </button>
             </div>
           </div>
           <SkeletonLine />
         </div>
         <div className="space-y-1 rounded-2xl p-[2px] border border-border">
           <div className="rounded-xl border border-border bg-background/60 relative px-3 py-2">
             <div className="flex items-center justify-between">
             <p className="text-xs text-muted-foreground uppercase tracking-wider">Card</p>
            <button
              className="text-[10px] font-mono border-b border-border"
              onClick={() => copy('Card', cmds.Card)}
              title="Copy CLI"
            >
              {copied === 'Card' ? <span className="text-emerald-500">Copied</span> : 'CLI'}
            </button>
            </div>
          </div>
          <SkeletonCard />
        </div>
        <div className="space-y-1 rounded-2xl p-[2px] border border-border">
          <div className="rounded-xl border border-border bg-background/60 relative px-3 py-2">
            <div className="flex items-center justify-between">
            <p className="text-xs text-muted-foreground uppercase tracking-wider">Profile</p>
            <button
              className="text-[10px] font-mono border-b border-border"
              onClick={() => copy('Profile', cmds.Profile)}
              title="Copy CLI"
            >
              {copied === 'Profile' ? <span className="text-emerald-500">Copied</span> : 'CLI'}
            </button>
            </div>
          </div>
          <SkeletonProfile />
        </div>
        <div className="space-y-1 rounded-2xl p-[2px] border border-border">
          <div className="rounded-xl border border-border bg-background/60 relative px-3 py-2">
            <div className="flex items-center justify-between">
            <p className="text-xs text-muted-foreground uppercase tracking-wider">Dashboard</p>
            <button
              className="text-[10px] font-mono border-b border-border"
              onClick={() => copy('Dashboard', cmds.Dashboard)}
              title="Copy CLI"
            >
              {copied === 'Dashboard' ? <span className="text-emerald-500">Copied</span> : 'CLI'}
            </button>
            </div>
          </div>
          <SkeletonDashboard />
        </div>
        <div className="space-y-1 rounded-2xl p-[2px] border border-border">
          <div className="rounded-xl border border-border bg-background/60 relative px-3 py-2">
            <div className="flex items-center justify-between">
            <p className="text-xs text-muted-foreground uppercase tracking-wider">Table</p>
            <button
              className="text-[10px] font-mono border-b border-border"
              onClick={() => copy('Table', cmds.Table)}
              title="Copy CLI"
            >
              {copied === 'Table' ? <span className="text-emerald-500">Copied</span> : 'CLI'}
            </button>
            </div>
          </div>
          <SkeletonTable />
        </div>
      </div>
      <div className="space-y-4">
        <div className="space-y-1 rounded-2xl p-[2px] border border-border">
          <div className="rounded-xl border border-border bg-background/60 relative px-3 py-2">
            <div className="flex items-center justify-between">
            <p className="text-xs text-muted-foreground uppercase tracking-wider">Hero</p>
            <button
              className="text-[10px] font-mono border-b border-border"
              onClick={() => copy('Hero', cmds.Hero)}
              title="Copy CLI"
            >
              {copied === 'Hero' ? <span className="text-emerald-500">Copied</span> : 'CLI'}
            </button>
            </div>
          </div>
          <SkeletonHero />
        </div>
        <div className="space-y-1 rounded-2xl p-[2px] border border-border">
          <div className="rounded-xl border border-border bg-background/60 relative px-3 py-2">
            <div className="flex items-center justify-between">
            <p className="text-xs text-muted-foreground uppercase tracking-wider">Sidebar</p>
            <button
              className="text-[10px] font-mono border-b border-border"
              onClick={() => copy('Sidebar', cmds.Sidebar)}
              title="Copy CLI"
            >
              {copied === 'Sidebar' ? <span className="text-emerald-500">Copied</span> : 'CLI'}
            </button>
            </div>
          </div>
          <SkeletonSidebar />
        </div>
        <div className="space-y-1 rounded-2xl p-[2px] border border-border">
          <div className="rounded-xl border border-border bg-background/60 relative px-3 py-2">
            <div className="flex items-center justify-between">
            <p className="text-xs text-muted-foreground uppercase tracking-wider">List</p>
            <button
              className="text-[10px] font-mono border-b border-border"
              onClick={() => copy('List', cmds.List)}
              title="Copy CLI"
            >
              {copied === 'List' ? <span className="text-emerald-500">Copied</span> : 'CLI'}
            </button>
            </div>
          </div>
          <SkeletonList />
        </div>
        <div className="space-y-1 rounded-2xl p-[2px] border border-border">
          <div className="rounded-xl border border-border bg-background/60 relative px-3 py-2">
            <div className="flex items-center justify-between">
            <p className="text-xs text-muted-foreground uppercase tracking-wider">Article</p>
            <button
              className="text-[10px] font-mono border-b border-border"
              onClick={() => copy('Article', cmds.Article)}
              title="Copy CLI"
            >
              {copied === 'Article' ? <span className="text-emerald-500">Copied</span> : 'CLI'}
            </button>
            </div>
          </div>
          <SkeletonArticle />
        </div>
        <div className="space-y-1 rounded-2xl p-[2px] border border-border">
          <div className="rounded-xl border border-border bg-background/60 relative px-3 py-2">
            <div className="flex items-center justify-between">
            <p className="text-xs text-muted-foreground uppercase tracking-wider">Grid</p>
            <button
              className="text-[10px] font-mono border-b border-border"
              onClick={() => copy('Grid', cmds.Grid)}
              title="Copy CLI"
            >
              {copied === 'Grid' ? <span className="text-emerald-500">Copied</span> : 'CLI'}
            </button>
            </div>
          </div>
          <SkeletonGrid />
        </div>
        <div className="space-y-1 rounded-2xl p-[2px] border border-border">
          <div className="rounded-xl border border-border bg-background/60 relative px-3 py-2">
            <div className="flex items-center justify-between">
            <p className="text-xs text-muted-foreground uppercase tracking-wider">Chat</p>
            <button
              className="text-[10px] font-mono border-b border-border"
              onClick={() => copy('Chat', cmds.Chat)}
              title="Copy CLI"
            >
              {copied === 'Chat' ? <span className="text-emerald-500">Copied</span> : 'CLI'}
            </button>
            </div>
          </div>
          <SkeletonChat />
        </div>
      </div>
    </div>
  )
}

Source Code

skeleton-sidebar.tsx
 'use client'
 
 import React from 'react'
 import { Skeleton } from './skeleton'
 
 export function SkeletonSidebar() {
   return (
     <div className="w-full max-w-md p-4 border rounded-xl bg-background/50 backdrop-blur-sm space-y-3">
       <Skeleton className="h-10 w-full rounded-md" />
       <div className="space-y-2">
         {[...Array(5)].map((_, i) => (
           <div key={i} className="flex items-center gap-3">
             <Skeleton className="h-6 w-6 rounded-md" />
             <Skeleton className="h-3 w-1/2" />
           </div>
         ))}
       </div>
     </div>
   )
 }

Source Code

skeleton-table.tsx
 'use client'
 
 import React from 'react'
 import { Skeleton } from './skeleton'
 
 export function SkeletonTable() {
   return (
     <div className="w-full max-w-md p-4 border rounded-xl bg-background/50 backdrop-blur-sm">
       <div className="h-8 w-full mb-3 flex items-center">
         <Skeleton className="h-6 w-24 rounded-md" />
         <div className="flex-1" />
         <Skeleton className="h-6 w-20 rounded-md" />
       </div>
       <div className="space-y-2">
         {[...Array(4)].map((_, i) => (
           <div key={i} className="grid grid-cols-3 gap-2">
             <Skeleton className="h-5 w-full" />
             <Skeleton className="h-5 w-full" />
             <Skeleton className="h-5 w-full" />
           </div>
         ))}
       </div>
     </div>
   )
 }

Dependencies

  • framer-motion: latest
  • clsx: latest
  • tailwind-merge: latest

Props

Component property reference.

NameTypeDefaultDescription
classNamestringundefinedAdditional CSS classes for styling
widthstring | numberundefinedWidth of the skeleton container
heightstring | numberundefinedHeight of the skeleton container
circlebooleanfalseWhether to render as a perfect circle (useful for avatars)
showbooleantrueConditional rendering flag
Context Worth Keeping In Orbit

Most components here are inspired by outstanding libraries and creators in the ecosystem. I don’t claim to be the original author — this is my space for learning, rebuilding, and understanding great work at a deeper level.

I’m still a student of the craft, constantly studying the best and translating what I learn through my own perspective. Every piece reflects curiosity, respect for the community, and small creative touches that feel true to me.

I’ve done my best to credit inspirations properly. If anything is missing or inaccurate, I truly appreciate a message so it can be corrected with care.