Skip to content

Skeleton loading screens in CSS, with a shimmer

A skeleton screen shows the shape of the content while it loads, so the page feels faster than a spinner. How to build one in pure CSS with a moving gradient, when to use it instead of a spinner, and how to keep it accessible.

Short answer: draw grey blocks where the content will appear (a circle for the avatar, bars for the lines of text), give them a gradient background four times as wide as the block, and move its background-position from right to left in a loop. That moving light is the shimmer. Mark the container aria-busy="true" and stop the shimmer for people who prefer reduced motion.

What is the code?

<div class="skeleton" aria-busy="true" aria-label="Loading">
  <i></i><span></span><span></span><span></span>
</div>
.skeleton {
  display: grid;
  grid-template-columns: 44px 1fr;
  gap: 10px 12px;
  width: 220px;
  padding: 14px;
  border-radius: 14px;
}
.skeleton i { grid-row: span 3; width: 44px; height: 44px; border-radius: 50%; }
.skeleton span { height: 10px; border-radius: 5px; }
.skeleton span:last-child { width: 60%; }

.skeleton i, .skeleton span {
  background: linear-gradient(90deg, #2a2635 25%, #3d3850 37%, #2a2635 63%) 0 0 / 400% 100%;
  animation: skeleton 1.4s ease-in-out infinite;
}
@keyframes skeleton {
  0%   { background-position: 100% 50% }
  100% { background-position: 0 50% }
}
@media (prefers-reduced-motion: reduce) {
  .skeleton i, .skeleton span { animation: none; }
}

How does the shimmer work?

The gradient is the base colour, a lighter band, and the base colour again. 400% 100% makes it four times wider than each block, so only a slice of it shows at a time. Moving background-position from 100% to 0 slides the light band across. Every block uses the same timing, so the light seems to pass over the whole card at once.

On a light page, swap the colours: a pale grey base such as #e9e9ee with a near-white band #f6f6f9.

Skeleton or spinner?

Use a skeleton whenUse a spinner when
You know the layout of what is coming: a list, a card, a profileYou do not know what is coming, or it has no layout
Loading takes about one to ten secondsThe wait is short or a single action, like saving
The content fills a large part of the screenThe wait is inside a small control, like a button

Under about a second, show neither: a flash of grey that disappears at once looks like a glitch. Delay the skeleton by a few hundred milliseconds, so fast loads never show it.

How closely should it match the content?

A skeleton helps only if it looks like what replaces it. Use the same sizes, spacing and number of lines as the real card, so nothing jumps when the content arrives. Avoid layout shift: when the real text is a different height, the page moves under the reader's finger.

Keep it calm. A slow shimmer (about 1.2 to 1.6 seconds per pass) reads as "loading"; a fast one reads as alarm. Use the same skeleton style across the whole product.

How do you keep loading accessible?

  • aria-busy="true" on the area that is loading tells assistive technology to wait; set it to false or remove it when the content is in.
  • A label like aria-label="Loading" gives the area a name while it has no text.
  • Announce the result if it matters, for example "12 results" in a polite live region, rather than announcing every skeleton.
  • Reduced motion: without the shimmer the grey blocks still say "something is coming".

Where can you check this?

How do you make your own?

Motion Studio has this Skeleton shimmer in its UI feedback kind, among 150 motions. Pick the colour of the bones and of the shine, set the duration and the easing, check it on a dark and a light stage, and copy the HTML and CSS or a React, Vue or Svelte component. Its CSS ends with the reduced-motion rule, so you do not have to remember it.

The tool

Do it in your browser.

Design

Motion Studio

150 ready-made motion graphics, from loaders and text effects to animated backgrounds, hover effects, charts and cards that follow the pointer, all playing at once: change colours, size, speed and easing, then take HTML and CSS, React, Vue or Svelte, or a GIF, a video or an animated SVG.

Keep reading

More from the blog.

Every post

Contact