Component Library

← Back to gallery

Skeleton loader

Category: Feedback · Tags: skeleton, loading, placeholder, aria-busy, shimmer

Loading comments…

How to use it

Use while a known layout is loading -- a list of comments or cards -- so the page does not jump when content arrives; keep the shapes close to the real layout and show it for no longer than the request takes. When the data arrives, replace the placeholder children and set aria-busy="false". For a short wait with no known layout show an indeterminate indicator (as in the loading button), and for a measurable task use the progress bar.

Accessibility

The container is a live region with aria-busy="true" while loading, so screen readers hold back announcements until you set it to false and insert the real content; the grey shapes are aria-hidden, and a visually hidden "Loading comments…" text says what is happening. The shimmer moves with transform only, and under prefers-reduced-motion: reduce the animation is switched off and the shapes stay still. The only text, the hidden label, is #0f172a on white (17.9:1); the placeholder shapes (#e2e8f0) are decorative.

Source (copy-ready)

<div class="dcl-skeleton" aria-busy="true" aria-live="polite">
  <span class="dcl-skeleton__sr">Loading comments&hellip;</span>
  <div class="dcl-skeleton__item" aria-hidden="true">
    <span class="dcl-skeleton__block dcl-skeleton__block--avatar"><span class="dcl-skeleton__shine"></span></span>
    <div class="dcl-skeleton__lines">
      <span class="dcl-skeleton__block dcl-skeleton__block--short"><span class="dcl-skeleton__shine"></span></span>
      <span class="dcl-skeleton__block"><span class="dcl-skeleton__shine"></span></span>
      <span class="dcl-skeleton__block dcl-skeleton__block--medium"><span class="dcl-skeleton__shine"></span></span>
    </div>
  </div>
  <div class="dcl-skeleton__item" aria-hidden="true">
    <span class="dcl-skeleton__block dcl-skeleton__block--avatar"><span class="dcl-skeleton__shine"></span></span>
    <div class="dcl-skeleton__lines">
      <span class="dcl-skeleton__block dcl-skeleton__block--short"><span class="dcl-skeleton__shine"></span></span>
      <span class="dcl-skeleton__block dcl-skeleton__block--medium"><span class="dcl-skeleton__shine"></span></span>
    </div>
  </div>
</div>
<style>
.dcl-skeleton { position: relative; max-width: 30rem; font: inherit; color: #0f172a; background-color: #ffffff; }
.dcl-skeleton__sr { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0; color: #0f172a; background-color: #ffffff; }
.dcl-skeleton__item { display: flex; gap: 0.75rem; padding: 0.75rem 0; }
.dcl-skeleton__lines { display: flex; flex-direction: column; gap: 0.5rem; flex: 1; min-width: 0; }
.dcl-skeleton__block { position: relative; display: block; overflow: hidden; height: 0.75rem; width: 100%; border-radius: 0.25rem; background-color: #e2e8f0; }
.dcl-skeleton__block--avatar { flex: none; width: 2.5rem; height: 2.5rem; border-radius: 50%; }
.dcl-skeleton__block--short { width: 35%; }
.dcl-skeleton__block--medium { width: 70%; }
.dcl-skeleton__shine { position: absolute; top: 0; bottom: 0; left: 0; width: 40%; background-color: #f1f5f9; transform: translateX(-100%); animation: dcl-skeleton-sweep 1.4s ease-in-out infinite; }
@keyframes dcl-skeleton-sweep {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(260%); }
}
@media (prefers-reduced-motion: reduce) {
  .dcl-skeleton__shine { animation: none; display: none; }
}
</style>

Was this useful? Send feedback.