Diwa Design System

Skeleton

Skeleton screens replace content with animated placeholder shapes during loading. They reduce perceived wait time and prevent layout shift. Use the getSkeletonStyle() utility to generate theme-aware skeleton styles, or apply the CSS class directly.

Example

Skeleton placeholders in both light and dark themes. The shimmer runs at 2 s to feel natural — not rushed.

Skeleton Light

Skeleton Dark

Usage

Skeleton styles can illustrate the loading state of complex elements such as cards, lists, or entire page sections.

Do

  • Match the skeleton shape closely to the real content — use a box for images, short lines for headings, and longer lines for body text.
  • Apply aria-busy="true" and a descriptive aria-label to the skeleton container so screen readers announce the loading state.
  • Remove skeleton nodes entirely and replace them with real content once loading is complete.
  • Respect reduced-motion: disable the shimmer animation when prefers-reduced-motion: reduce is set.

Don't

  • Don't use skeleton screens for operations faster than 300 ms — a brief delay feels like a flash, not a loading state.
  • Don't leave skeletons visible indefinitely — always show an error or empty state if loading fails.
  • Don't animate multiple skeletons at different speeds; stagger delays (0.1 s increments) but keep the duration consistent at 2 s.
  • Don't use skeletons for simple single-value fields — a spinner or inline placeholder is more appropriate.

Animation speed: The shimmer runs at 2 s — matching Diwa guidance. Faster animations (under 1.5 s) feel jittery and anxious; slower ones (over 2.5 s) feel stalled. Do not override animation-duration on individual skeletons.

Styles

Use the JS utility for CSS-in-JS or inline styles. Use the CSS class directly for stylesheet-based projects. Always include the reduced-motion reset.

// JS
import { getSkeletonStyle } from '@diwacopilot/components/styles';

// Returns a CSS-in-JS object with the shimmer gradient + animation
const lightSkeleton = getSkeletonStyle({ theme: 'light' });
const darkSkeleton  = getSkeletonStyle({ theme: 'dark' });

// Usage in React
<div style={{ ...lightSkeleton, height: '160px', borderRadius: '8px' }} />
<div style={{ ...lightSkeleton, height: '14px', width: '60%' }} />

/* ─── CSS ─────────────────────────────────────────────────────────────── */
@keyframes diwa-skeleton-pulse {
  0%   { background-position: -200% center; }
  100% { background-position:  200% center; }
}

/* Light theme */
.skeleton--light {
  background: linear-gradient(
    90deg,
    rgba(0, 0, 0, 0.04) 25%,
    rgba(0, 0, 0, 0.10) 50%,
    rgba(0, 0, 0, 0.04) 75%
  );
  background-size: 200% 100%;
  animation: diwa-skeleton-pulse 2s ease-in-out infinite;
  border-radius: 4px;
}

/* Dark theme */
.skeleton--dark {
  background: linear-gradient(
    90deg,
    rgba(255, 255, 255, 0.04) 25%,
    rgba(255, 255, 255, 0.12) 50%,
    rgba(255, 255, 255, 0.04) 75%
  );
  background-size: 200% 100%;
  animation: diwa-skeleton-pulse 2s ease-in-out infinite;
  border-radius: 4px;
}

/* Stagger delay for multiple lines */
.skeleton:nth-child(2) { animation-delay: 0.1s; }
.skeleton:nth-child(3) { animation-delay: 0.2s; }

/* Accessibility — reduced motion */
@media (prefers-reduced-motion: reduce) {
  .skeleton--light,
  .skeleton--dark {
    animation: none;
    background: rgba(0, 0, 0, 0.06);
  }
}

/* Accessibility — ARIA */
<div aria-busy="true" aria-label="Loading content">
  <div class="skeleton--light" style="height:14px; width:60%" />
  <div class="skeleton--light" style="height:14px; width:85%" />
</div>
@use '@diwacopilot/components/styles' as *;

@keyframes diwa-skeleton-pulse {
  0%   { background-position: -200% center; }
  100% { background-position:  200% center; }
}

.skeleton {
  background: linear-gradient(
    90deg,
    rgba(255, 255, 255, 0.04) 25%,
    rgba(255, 255, 255, 0.12) 50%,
    rgba(255, 255, 255, 0.04) 75%
  );
  background-size: 200% 100%;
  animation: diwa-skeleton-pulse 2s ease-in-out infinite;
  border-radius: $diwa-border-radius-sm;
}

@media (prefers-reduced-motion: reduce) {
  .skeleton { animation: none; }
}