Skeleton
<div style="display: flex; align-items: center; gap: 0.75rem"> <div class="skeleton" style="width: 2.5rem; height: 2.5rem; border-radius: 9999px"></div> <div style="display: grid; gap: 0.5rem"> <div class="skeleton" style="width: 12rem; height: 0.75rem"></div> <div class="skeleton" style="width: 8rem; height: 0.75rem"></div> </div></div><div class="skeleton" style="width: 12rem; height: 0.75rem"></div>The class paints and pulses; the size is yours, because a placeholder should match the shape of what is coming.
In a card
Section titled “In a card”<div class="card" style="max-width: 20rem"> <div class="card-header"> <div class="skeleton" style="width: 60%; height: 1rem"></div> <div class="skeleton" style="width: 90%; height: 0.75rem"></div> </div> <div class="card-content"> <div class="skeleton" style="width: 100%; height: 6rem"></div> </div></div>Reduced motion
Section titled “Reduced motion”The pulse stops under prefers-reduced-motion and settles at a steady tone. A placeholder that
never stops moving is a distraction, and for some people a trigger.
Set dir="rtl" on the page or on any subtree — padding, corners and positions follow the reading
direction on their own. See the button page for a live example.
Accessibility
Section titled “Accessibility”A skeleton is decoration: it says nothing to a screen reader, which is right — announcing “loading” once beats announcing a dozen grey boxes.
Put the announcement on the region instead:
<div aria-busy="true" aria-live="polite"> <div class="skeleton" style="height: 1rem"></div></div>Swap aria-busy to false when the content lands, and the region announces itself once.