Skeleton

Placeholder representing loading content.

Loading content

html

<div class="skeleton skeleton--text skeleton--animated" role="status" aria-label="Loading content">
  <span class="skeleton__srtext">Loading content</span>
  <div class="skeleton__avatar" aria-hidden="true"></div>
  <div class="skeleton__lines" aria-hidden="true" style="--lines:3;">
    <span class="skeleton__line"></span><span class="skeleton__line"></span><span class="skeleton__line"></span>
    <span class="skeleton__line"></span><span class="skeleton__line"></span><span class="skeleton__line"></span>
  </div>
</div>
<style>
.skeleton { display: flex; align-items: flex-start; gap: 12px; width: 260px; }
.skeleton__srtext { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
.skeleton__avatar { display: none; width: 44px; height: 44px; border-radius: 50%; background: #24143018; flex-shrink: 0; }
.skeleton--avatar .skeleton__avatar { display: block; }
.skeleton--card { flex-direction: column; }
.skeleton--card .skeleton__avatar { display: block; width: 100%; height: 120px; border-radius: 10px; }
.skeleton__lines { flex: 1; display: flex; flex-direction: column; gap: 8px; }
.skeleton__line { display: none; height: 10px; border-radius: 5px; background: #24143018; }
.skeleton__line:nth-child(1) { display: block; width: 100%; }
.skeleton__lines[style*="--lines:2"] .skeleton__line:nth-child(2),
.skeleton__lines[style*="--lines:3"] .skeleton__line:nth-child(2),
.skeleton__lines[style*="--lines:4"] .skeleton__line:nth-child(2),
.skeleton__lines[style*="--lines:5"] .skeleton__line:nth-child(2),
.skeleton__lines[style*="--lines:6"] .skeleton__line:nth-child(2) { display: block; width: 92%; }
.skeleton__lines[style*="--lines:3"] .skeleton__line:nth-child(3),
.skeleton__lines[style*="--lines:4"] .skeleton__line:nth-child(3),
.skeleton__lines[style*="--lines:5"] .skeleton__line:nth-child(3),
.skeleton__lines[style*="--lines:6"] .skeleton__line:nth-child(3) { display: block; width: 96%; }
.skeleton__lines[style*="--lines:4"] .skeleton__line:nth-child(4),
.skeleton__lines[style*="--lines:5"] .skeleton__line:nth-child(4),
.skeleton__lines[style*="--lines:6"] .skeleton__line:nth-child(4) { display: block; width: 88%; }
.skeleton__lines[style*="--lines:5"] .skeleton__line:nth-child(5),
.skeleton__lines[style*="--lines:6"] .skeleton__line:nth-child(5) { display: block; width: 94%; }
.skeleton__lines[style*="--lines:6"] .skeleton__line:nth-child(6) { display: block; width: 70%; }
.skeleton--animated .skeleton__avatar, .skeleton--animated .skeleton__line {
  background: linear-gradient(90deg, #24143018 25%, #2414301f 37%, #24143018 63%); background-size: 400% 100%; }
@keyframes skeleton-shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }
@media (prefers-reduced-motion: no-preference) {
  .skeleton--animated .skeleton__avatar, .skeleton--animated .skeleton__line { animation: skeleton-shimmer .2s ease infinite; }
}
</style>

tailwind

<div role="status" aria-label="Loading content" class="skeleton-tw skeleton-tw--text skeleton-tw--animated flex items-start gap-3 w-64">
  <span class="sr-only">Loading content</span>
  <div aria-hidden="true" class="skeleton-tw__avatar hidden w-11 h-11 rounded-full bg-[#24143018] flex-shrink-0"></div>
  <div aria-hidden="true" class="skeleton-tw__lines flex-1 flex flex-col gap-2" style="--lines:3;">
    <span class="skeleton-tw__line block h-2.5 rounded-full bg-[#24143018] w-full"></span>
    <span class="skeleton-tw__line h-2.5 rounded-full bg-[#24143018] w-[92%]"></span>
    <span class="skeleton-tw__line h-2.5 rounded-full bg-[#24143018] w-[96%]"></span>
    <span class="skeleton-tw__line h-2.5 rounded-full bg-[#24143018] w-[88%]"></span>
    <span class="skeleton-tw__line h-2.5 rounded-full bg-[#24143018] w-[94%]"></span>
    <span class="skeleton-tw__line h-2.5 rounded-full bg-[#24143018] w-[70%]"></span>
  </div>
</div>
<style>
.skeleton-tw--avatar .skeleton-tw__avatar { display: block; }
.skeleton-tw--card { flex-direction: column; }
.skeleton-tw--card .skeleton-tw__avatar { display: block; width: 100%; height: 120px; border-radius: 10px; }
.skeleton-tw__lines > .skeleton-tw__line:nth-child(n+2) { display: none; }
.skeleton-tw__lines[style*="--lines:2"] > .skeleton-tw__line:nth-child(2),
.skeleton-tw__lines[style*="--lines:3"] > .skeleton-tw__line:nth-child(-n+3),
.skeleton-tw__lines[style*="--lines:4"] > .skeleton-tw__line:nth-child(-n+4),
.skeleton-tw__lines[style*="--lines:5"] > .skeleton-tw__line:nth-child(-n+5),
.skeleton-tw__lines[style*="--lines:6"] > .skeleton-tw__line:nth-child(-n+6) { display: block; }
.skeleton-tw--animated .skeleton-tw__avatar, .skeleton-tw--animated .skeleton-tw__line {
  background: linear-gradient(90deg, #24143018 25%, #2414301f 37%, #24143018 63%); background-size: 400% 100%; }
@keyframes skeleton-tw-shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }
@media (prefers-reduced-motion: no-preference) {
  .skeleton-tw--animated .skeleton-tw__avatar, .skeleton-tw--animated .skeleton-tw__line { animation: skeleton-tw-shimmer .2s ease infinite; }
}
</style>

react

export function Skeleton({
  label = "Loading content",
  shape = "text",
  lines = 3,
  animated = true,
}) {
  const widths = ["100%", "92%", "96%", "88%", "94%", "70%"];
  const shimmer = animated
    ? { background: "linear-gradient(90deg, #24143018 25%, #2414301f 37%, #24143018 63%)", backgroundSize: "400% 100%" }
    : { background: "#24143018" };
  const shimmerClass = animated ? "skel-shimmer" : undefined;
  return (
    <div role="status" aria-label={label} style={{ display: "flex", alignItems: shape === "card" ? "stretch" : "flex-start",
      flexDirection: shape === "card" ? "column" : "row", gap: 12, width: 260 }}>
      {animated && <span style={{ position: "absolute", width: 1, height: 1, overflow: "hidden", clip: "rect(0 0 0 0)" }}>{label}</span>}
      {shape !== "text" && (
        <div aria-hidden="true" className={shimmerClass} style={{ width: shape === "card" ? "100%" : 44, height: shape === "card" ? 120 : 44,
          borderRadius: shape === "card" ? 10 : "50%", flexShrink: 0, ...shimmer }} />
      )}
      <div aria-hidden="true" style={{ flex: 1, display: "flex", flexDirection: "column", gap: 8 }}>
        {widths.slice(0, lines).map((w, i) => (
          <span key={i} className={shimmerClass} style={{ height: 10, borderRadius: 5, width: w, ...shimmer }} />
        ))}
      </div>
      <style>{`
        @media (prefers-reduced-motion: no-preference) {
          .skel-shimmer { animation: skeleton-react-shimmer 1.4s ease infinite; }
          @keyframes skeleton-react-shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }
        }
      `}</style>
    </div>
  );
}