Skip to content

LoadingSkeleton

SM · 950B gzip budget

Animated placeholder shapes for content loading states.

Category
Feedback & status
Budget
sm, single-purpose control
External runtime
React / React DOM only
axe fixtureSSR rendersource scanbundle budget

Preview

Live fixture
Preview locale

Try the component here. Locale-aware formatting and direction follow the selected language; example content and labels are not automatically translated. Overlay demos use interactive launchers.

Install

npx shadcn@latest add https://gear5-ui.vercel.app/r/loading-skeleton.json

Copies the source into your project.

New here? Set up Tailwind and import aliases first →

Source & props

Edit on GitHub ↗

Prop types and inline documentation are included below. This is repository source; the installer rewrites shared imports for your project.

"use client";

import React from "react";

export interface LoadingSkeletonProps {
  width?: string | number;
  height?: string | number;
  variant?: "text" | "circular" | "rectangular";
  lines?: number;
  style?: React.CSSProperties;
}

const shimmerKeyframes = `
@keyframes shimmer {
  0% { background-position: -200% 0; }
  100% { background-position: 200% 0; }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
`;

export function LoadingSkeleton({
  width = "100%",
  height = "1rem",
  variant = "text",
  lines = 1,
  style,
}: LoadingSkeletonProps) {
  const baseStyle: React.CSSProperties = {
    width,
    height: variant === "circular" ? width : height,
    borderRadius:
      variant === "circular"
        ? "50%"
        : variant === "rectangular"
          ? "0.5rem"
          : "0.25rem",
    background:
      "linear-gradient(90deg, #e0e0e0 25%, #f0f0f0 50%, #e0e0e0 75%)",
    backgroundSize: "200% 100%",
    animation: "shimmer 1.5s infinite linear",
    ...style,
  };

  if (variant === "text" && lines > 1) {
    return (
      <>
        <style>{shimmerKeyframes}</style>
        <div style={{ display: "flex", flexDirection: "column", gap: "0.5rem" }}>
          {Array.from({ length: lines }).map((_, i) => (
            <div
              key={i}
              aria-hidden="true"
              style={{
                ...baseStyle,
                width: i === lines - 1 ? "60%" : "100%",
              }}
            />
          ))}
        </div>
      </>
    );
  }

  return (
    <>
      <style>{shimmerKeyframes}</style>
      <div aria-hidden="true" style={baseStyle} />
    </>
  );
}

What CI checks

Quality contract
  • Bundled, minified and gzipped against its tier budget
  • Audited by axe in the state previewed above
  • Rendered through react-dom/server with no browser globals
  • Scanned for network calls, dangerous sinks, and unguarded animation