🔥 Oxide UI v0.1.0 — Dark mode only, copy-paste ready. Get started →
Skip to content

Skeleton

<div class="animate-pulse">

Animated placeholder elements that mirror the layout of incoming content. Reduces perceived load time by showing structure immediately.

Overview

Preview

Base skeleton

All skeletons use animate-pulse from Tailwind and bg-zinc-800. Adjust size with width/height utilities.

Preview
html
<!-- Text lines -->
<div class="h-4 w-3/4 rounded bg-zinc-800 animate-pulse"></div>
<div class="h-3 w-full  rounded bg-zinc-800 animate-pulse"></div>
<div class="h-3 w-5/6  rounded bg-zinc-800 animate-pulse"></div>

<!-- Avatar circle -->
<div class="h-10 w-10 rounded-full bg-zinc-800 animate-pulse"></div>

<!-- Button -->
<div class="h-9 w-24 rounded-md bg-zinc-800 animate-pulse"></div>

<!-- Badge -->
<div class="h-5 w-14 rounded-full bg-zinc-800 animate-pulse"></div>

Card skeleton

Preview
html
<div class="rounded-xl border border-zinc-800 bg-zinc-900/50 p-5 space-y-3">
  <!-- Avatar + name -->
  <div class="flex items-center gap-3">
    <div class="h-9 w-9 shrink-0 rounded-full bg-zinc-800 animate-pulse"></div>
    <div class="space-y-1.5 flex-1">
      <div class="h-3 w-24 rounded bg-zinc-800 animate-pulse"></div>
      <div class="h-2.5 w-16 rounded bg-zinc-800/60 animate-pulse"></div>
    </div>
  </div>
  <!-- Body text -->
  <div class="space-y-2">
    <div class="h-2.5 w-full rounded bg-zinc-800 animate-pulse"></div>
    <div class="h-2.5 w-5/6 rounded bg-zinc-800 animate-pulse"></div>
    <div class="h-2.5 w-4/6 rounded bg-zinc-800/60 animate-pulse"></div>
  </div>
  <!-- Buttons -->
  <div class="flex gap-2">
    <div class="h-7 w-20 rounded-md bg-zinc-800 animate-pulse"></div>
    <div class="h-7 w-16 rounded-md bg-zinc-800/60 animate-pulse"></div>
  </div>
</div>

Table skeleton

html
<div class="rounded-xl border border-zinc-800 overflow-hidden">
  <!-- Header -->
  <div class="flex gap-4 border-b border-zinc-800 bg-zinc-900/30 px-5 py-3">
    <div class="h-2.5 w-20 rounded bg-zinc-800/80 animate-pulse"></div>
    <div class="h-2.5 w-24 rounded bg-zinc-800/80 animate-pulse ml-auto"></div>
    <div class="h-2.5 w-16 rounded bg-zinc-800/80 animate-pulse"></div>
  </div>
  <!-- Rows -->
  <div v-for="i in 5" :key="i" class="flex items-center gap-4 border-b border-zinc-800/50 px-5 py-3">
    <div class="h-7 w-7 rounded-full bg-zinc-800 animate-pulse shrink-0"></div>
    <div class="h-2.5 flex-1 rounded bg-zinc-800 animate-pulse"></div>
    <div class="h-4 w-16 rounded-full bg-zinc-800/60 animate-pulse ml-auto"></div>
  </div>
</div>

With Vue (show real content when loaded)

vue
<script setup>
const { data, pending } = await useFetch('/api/posts')
</script>

<template>
  <!-- Skeleton while loading -->
  <div v-if="pending" class="space-y-3">
    <div v-for="i in 3" :key="i" class="rounded-xl border border-zinc-800 p-5">
      <div class="flex items-center gap-3">
        <div class="h-9 w-9 rounded-full bg-zinc-800 animate-pulse"></div>
        <div class="space-y-2 flex-1">
          <div class="h-3 w-1/3 rounded bg-zinc-800 animate-pulse"></div>
          <div class="h-2.5 w-1/2 rounded bg-zinc-800 animate-pulse"></div>
        </div>
      </div>
    </div>
  </div>

  <!-- Real content -->
  <div v-else class="space-y-3">
    <PostCard v-for="post in data" :key="post.id" :post="post" />
  </div>
</template>

Shimmer variant

For a more dynamic feel, replace animate-pulse with a shimmer sweep:

html
<div class="h-4 w-full overflow-hidden rounded bg-zinc-800 relative">
  <div class="absolute inset-0 -translate-x-full bg-gradient-to-r from-transparent via-zinc-700/30 to-transparent animate-[shimmer_1.5s_ease-in-out_infinite]"></div>
</div>

Add to tailwind.config.js:

js
keyframes: {
  shimmer: {
    '0%':   { transform: 'translateX(-100%)' },
    '100%': { transform: 'translateX(200%)' },
  },
},

Accessibility notes

  • Skeleton containers should have aria-busy="true" while loading and aria-busy="false" when content is ready
  • Add aria-label="Loading..." to the skeleton container for screen readers
  • Remove skeletons from the DOM (don't hide with opacity-0) once content loads

Released under the MIT License.