← Back to gallery
CSS

Layout-stable Skeleton Placeholder

A project-authored CSS skeleton loader showcase that reserves media and text slots up front so shimmer placeholders swap to loaded content without causing layout shift.

skeleton-loaderplaceholderlayout-stabilitycls

Skeleton / placeholder / layout stability / CLS

Layout-stable skeleton placeholder

Reimplements skeleton placeholders with explicit reserved media and copy slots so shimmer improves perceived performance without shifting layout when real content arrives.

all cards are reserving space before load

Editorial summary card

Article Rail

Article Rail reserves a fixed card frame before content appears, keeping the thumbnail, title, and metadata rows in place while the shimmer state hints at incoming copy.

reserved 3:2 thumbnail + four copy rows

Skeleton slots: thumbnail, eyebrow, title, and metadata slots.

  • editorial
  • summary rail
  • stable thumbnail

Dashboard insight card

Signal Analytics Board

Signal Analytics Board locks a chart frame and metric band first, so incoming dashboard data can replace placeholders without pushing nearby UI or creating CLS.

reserved 16:10 chart frame

Skeleton slots: metric stripe, chart frame, and footer chips.

  • dashboard
  • chart slot
  • cls-safe loading

Avatar + bio layout

Team Profile Dock

Team Profile Dock pre-allocates avatar, identity, and bio heights so loading people cards stay aligned even when the final text length varies across team members.

reserved avatar rail + bio stack

Skeleton slots: avatar circle, identity lines, and action row.

  • people card
  • avatar slot
  • stable actions

CLS guardrails

Article Rail

Reserved space
reserved 3:2 thumbnail + four copy rows
Skeleton slots
thumbnail, eyebrow, title, and metadata slots
Frame sizing
Cards use fixed min-heights plus media aspect ratios so loading and loaded states share the same footprint.
Motion policy
Reduced-motion users get a settled highlight instead of a constantly sweeping shimmer lane.
Touch targets
Each preset keeps a full-width action button on mobile to preserve a comfortable tap area.

Reserve structural space first, then let content swap into that frame. The placeholder should communicate progress, not borrow height from neighboring cards.

css
.stable-frame {
  min-height: 252px;
  display: grid;
  gap: 14px;
  padding: 16px;
  border-radius: 18px;
  background: linear-gradient(180deg, rgba(11, 17, 32, 0.96), rgba(6, 10, 20, 0.96));
}

.stable-surface {
  position: relative;
  overflow: hidden;
  background: linear-gradient(
    90deg,
    rgba(30, 41, 59, 0.92),
    rgba(59, 130, 246, 0.18),
    rgba(30, 41, 59, 0.92)
  );
  background-size: 220% 100%;
  animation: shimmerSweep 2.3s linear infinite;
}

.stable-frame[data-loaded='true'] .stable-surface {
  animation: none;
}

@keyframes shimmerSweep {
  0%   { background-position: 0% 0; }
  100% { background-position: -220% 0; }
}

/* Article Rail — 3:2 thumbnail + four copy rows */
.stable-frame--article {
  grid-template-rows: minmax(0, 1fr) auto;
}

.stable-frame__media--thumbnail {
  aspect-ratio: 3 / 2;
  border-radius: 16px;
}

.stable-frame__content {
  display: grid;
  gap: 10px;
}

.stable-frame__line {
  height: 14px;
  border-radius: 999px;
}

.stable-frame__line--eyebrow { width: 34%; height: 12px; }
.stable-frame__line--title   { width: 82%; height: 18px; }
.stable-frame__line--body    { width: 64%; }