Skeletons and placeholders

Shimmer · pulse · static

Show the shape before the content

Skeletons tell people what is coming and where. They make waiting feel shorter and stop the layout from jumping when content arrives.

  • Use skeletons for the first load of content whose layout you know: lists, cards, tables, profiles, dashboards.
  • Match the real layout: same sizes, same spacing, same number of lines (2–3 for paragraphs).
  • Replace the whole skeleton at once when data arrives, with a 140ms fade. Don't fill it in piece by piece.
  • If loading takes more than ~10s, add a status line or switch to a progress indicator.

Three styles

StyleMotionUse
ShimmerA soft highlight sweeps left to right every 1.6s (right to left in Arabic)Default for content areas and cards
PulseOpacity breathes between 100% and 55% every 1.6sDense UI and tables where many shimmers would be busy
StaticNo motionReduced-motion preference, print, screenshots, and placeholders that are not loading

With prefers-reduced-motion the shimmer stops and skeletons become static; pulse slows down.

Anatomy and tokens

Shapes match the content

UserOrganisationModels
ElementSkeleton
Text line12px high, 6px radius, widths 100% / 80% / 60% (last line shorter)
Heading20px high, ~55% width
AvatarCircle at the real size
Image or chartBlock with the real aspect ratio, 16px radius
ButtonThe button's size and radius, no label
Number36px high, the width of the expected value
TokenLightDark
--n-skeleton-base#ECEEF2#1E2640
--n-skeleton-shinergba(255,255,255,0.65)rgba(255,255,255,0.06)

Skeleton to content

Swap on arrival

Ghost elements

Placeholders that invite action

Ghost elements are not loading states. They show where something can go: an empty slot in a dashboard, a drop zone, the next card in a builder. Dashed 1.5px border, transparent fill, caption text with an action.

  • Use them for empty slots that the user fills, never for content that is loading.
  • One clear action inside: “Add widget”, “Drop files”, “Add a lens”.

Requests

574k

Image placeholders

  • While an image loads, show its aspect-ratio box in the skeleton base colour (or the Dune tint for illustrations), never a broken-image icon.
  • Low-quality previews may blur in (--n-filter-blur-md) and sharpen over 220ms.
  • If an image fails, show the alt text in caption colour inside the box.

Code

<span class="n-skel n-skel--text n-skel--shimmer" style="width:80%"></span>
<span class="n-skel n-skel--circle n-skel--shimmer" style="width:40px;height:40px"></span>
<span class="n-skel n-skel--block n-skel--pulse" style="aspect-ratio:16/9"></span>
<div aria-busy="true" aria-live="polite">… skeleton …</div>   <!-- container announces when content arrives -->

<!-- Tailwind -->
<span class="block h-3 w-4/5 rounded-[6px] bg-n-skeleton-base relative overflow-hidden
  after:absolute after:inset-0 after:-translate-x-full after:animate-shimmer
  after:bg-linear-to-r after:from-transparent after:via-n-skeleton-shine after:to-transparent"></span>

Dos and don'ts

Do

  • Match the real layout and sizes.
  • Swap the whole skeleton at once.
  • Use static skeletons for reduced motion.
  • Use ghosts only for empty slots the user can fill.

Don't

  • Show skeletons for data that loads in under 300ms.
  • Shimmer every element on a dense screen at once.
  • Use brand colours or gradients in skeletons.
  • Keep a skeleton forever: time out into an error.

Last updated on 14 October 2026