AI CSS Animations

Social Media Post Skeleton

A feed post card: avatar, name and time, two lines of text, a media block and an action bar. The media shows drifting blurred color blobs, like a BlurHash placeholder, while the text breathes.

Also known as: feed post skeleton, social feed placeholder, tweet skeleton loader.

HTML

<div class="ldr-skeleton-social-post" role="status" aria-label="Loading content">
  <div class="ldr-skeleton-social-post-head"><span></span><span></span><span></span><span></span></div>
  <span class="ldr-skeleton-social-post-text"></span>
  <span class="ldr-skeleton-social-post-text"></span>
  <span class="ldr-skeleton-social-post-media"></span>
  <div class="ldr-skeleton-social-post-actions"><span></span><span></span><span></span></div>
</div>

CSS

.ldr-skeleton-social-post {
  --s: var(--ldr-size, 48px);
  --c: var(--ldr-color, #3b82f6);
  display: grid;
  gap: calc(var(--s) / 8);
  width: calc(var(--s) * 4.8);
  padding: calc(var(--s) / 5);
  border: 1px solid color-mix(in srgb, var(--ldr-color, #3b82f6) 16%, transparent);
  border-radius: calc(var(--s) / 5);
}
.ldr-skeleton-social-post-head {
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-rows: auto auto;
  column-gap: calc(var(--s) / 6);
  row-gap: calc(var(--s) / 12);
  align-items: center;
  margin-bottom: calc(var(--s) / 20);
}
.ldr-skeleton-social-post :where(span) {
  display: block;
  height: calc(var(--s) / 7);
  border-radius: var(--s);
  background: color-mix(in srgb, var(--ldr-color, #3b82f6) 20%, transparent);
}
.ldr-skeleton-social-post-head span:nth-child(1) {
  grid-row: span 2;
  width: calc(var(--s) * 0.52);
  height: calc(var(--s) * 0.52);
}
.ldr-skeleton-social-post-head span:nth-child(2) { width: 46%; align-self: end; }
.ldr-skeleton-social-post-head span:nth-child(3) {
  grid-row: span 2;
  width: calc(var(--s) / 2.4);
  height: calc(var(--s) / 12);
  background: radial-gradient(circle closest-side, color-mix(in srgb, var(--ldr-color, #3b82f6) 34%, transparent) 90%, transparent) 0 0 / 33.4% 100% repeat-x;
}
.ldr-skeleton-social-post-head span:nth-child(4) { width: 26%; height: calc(var(--s) / 9); align-self: start; background: color-mix(in srgb, var(--ldr-color, #3b82f6) 12%, transparent); }
.ldr-skeleton-social-post-text,
.ldr-skeleton-social-post-head span:nth-child(2),
.ldr-skeleton-social-post-head span:nth-child(4) {
  animation: ldr-skeleton-social-post-breathe 2s ease-in-out infinite;
}
.ldr-skeleton-social-post-text + .ldr-skeleton-social-post-text { width: 68%; }
.ldr-skeleton-social-post-media {
  height: calc(var(--s) * 1.15);
  margin: calc(var(--s) / 20) 0;
  border-radius: calc(var(--s) / 8);
  background:
    radial-gradient(38% 60% at 50% 50%, color-mix(in srgb, var(--ldr-color, #3b82f6) 50%, transparent), transparent 70%) 10% 20% / 170% 170% no-repeat,
    radial-gradient(34% 55% at 50% 50%, color-mix(in oklch, var(--c) 50%, rgb(236 72 153 / 0.45)), transparent 70%) 90% 80% / 170% 170% no-repeat,
    radial-gradient(40% 60% at 50% 50%, color-mix(in srgb, var(--ldr-color, #3b82f6) 30%, transparent), transparent 70%) 60% 0% / 170% 170% no-repeat,
    color-mix(in srgb, var(--ldr-color, #3b82f6) 14%, transparent);
  animation: ldr-skeleton-social-post-drift 5s ease-in-out infinite;
}
.ldr-skeleton-social-post-actions {
  display: flex;
  gap: calc(var(--s) / 2.5);
}
.ldr-skeleton-social-post-actions span {
  width: calc(var(--s) * 0.6);
  height: calc(var(--s) / 5);
  background: radial-gradient(circle closest-side, color-mix(in srgb, var(--ldr-color, #3b82f6) 28%, transparent) 92%, transparent) 0 50% / calc(var(--s) / 5) 100% no-repeat, linear-gradient(color-mix(in srgb, var(--ldr-color, #3b82f6) 16%, transparent) 0 0) 100% 50% / 50% 60% no-repeat;
}
@keyframes ldr-skeleton-social-post-breathe {
  50% { opacity: 0.5; }
}
@keyframes ldr-skeleton-social-post-drift {
  0%, 100% { background-position: 10% 20%, 90% 80%, 60% 0%, 0 0; }
  33%      { background-position: 80% 40%, 20% 70%, 30% 100%, 0 0; }
  66%      { background-position: 40% 90%, 60% 10%, 90% 40%, 0 0; }
}

@media (prefers-reduced-motion: reduce) {
  .ldr-skeleton-social-post, .ldr-skeleton-social-post *, .ldr-skeleton-social-post::before, .ldr-skeleton-social-post::after, .ldr-skeleton-social-post *::before, .ldr-skeleton-social-post *::after {
    animation-duration: 3s;
  }
}

Customizing this loader

  • Color: set --ldr-color on the element or a parent. Currently previewing #3b82f6.
  • Size: set --ldr-size. Every dimension in the snippet is derived from it. Currently previewing 64px.
  • Speed: change the duration on the animation shorthand. Delays on the children are relative, so they keep their rhythm.
  • Accessibility: the root carries role="status" and an aria-label so screen readers announce the wait. The reduced-motion rule at the end slows the animation for users who asked for less motion.

Similar loaders