Spinners say "wait"; skeletons say "almost there" — perceived speed improves even when actual speed doesn't.
The shimmer
.skeleton {
background: linear-gradient(90deg,
#1c1c27 25%, #2a2a38 50%, #1c1c27 75%);
background-size: 200% 100%;
animation: shimmer 1.4s infinite;
border-radius: 8px;
}
@keyframes shimmer {
to { background-position: -200% 0; }
}
/* shape the placeholders like real content */
.skel-title { height: 20px; width: 60%; }
.skel-text { height: 12px; width: 100%; margin-top: 10px; }
.skel-avatar { width: 48px; aspect-ratio: 1; border-radius: 50%; }The swap
async function load() {
card.innerHTML = skeletonHTML; // instant
const data = await fetchData();
card.innerHTML = renderCard(data); // replace when ready
}Rules
- Skeleton layout must match final layout — no layout jump on swap
- Use for content areas (cards, lists); spinners for actions (submitting)
- Respect
prefers-reduced-motion: freeze the shimmer