Instagram Brand Manual

Foundations

Loading & Skeletons

Instagram uses a shimmer skeleton pattern for first-load states and spinners only for user-triggered actions. The shimmer sweep timing and skeleton shapes are defined per-surface to prevent layout shift when content resolves.

Loading state system

Five loading modes — one for each scenario

The right loading pattern depends on whether the content shape is known, whether the user triggered the wait, and whether the user has seen the content before.

PatternUse whenUse forAvoid

Skeleton

Shimmer sweep (translateX + gradient)

First load or empty cache — the full page shape is unknown to the user

Feed, profile headers, story trays, explore grids

Never skeleton a page the user has already seen — use stale content + background refresh instead

Shimmer only (no shape)

Shimmer on a solid block

Content area dimensions are known but content isn't, e.g. a DM thread with known height

Message thread first load, Settings rows

Don't use for media — the aspect ratio matters too much for layout stability

Spinner

Rotating arc, --ig-primary-button colour, 1s linear

A user-triggered action with unknown completion time (submit, upload, refresh)

Pull-to-refresh, post submission, follow action on slow connection

Don't use spinner for initial page load — a skeleton is less disorienting

Progress bar

scaleX from 0 to 1, linear timing at the operation's own speed

An operation with known progress — typically file upload or Stories playback

Media upload, Story progress (see Stories Progress component)

Don't fake progress — if the operation duration is unknown, use a spinner instead

Stale content + background refresh

None — content stays visible, no loading state shown

The user has seen this content before and the cache is still valid

Feed revisit, profile revisit, returning to a conversation

Don't flash a skeleton when stale content is available — it creates a worse experience than showing old data

Shimmer anatomy

The shimmer sweep

The shimmer is a light-coloured gradient that sweeps left-to-right across a placeholder shape. It communicates 'content is loading' without suggesting a specific time-to-complete.

Shimmer CSS — the core sweep animation
/* Base skeleton shape */
.skeleton {
  background: rgb(var(--ig-highlight-background));
  border-radius: var(--radius-sm);
  position: relative;
  overflow: hidden;
}

/* The shimmer overlay */
.skeleton::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    90deg,
    transparent 0%,
    rgba(var(--ig-primary-background), 0.6) 50%,
    transparent 100%
  );
  transform: translateX(-100%);
  animation: shimmer 1.6s infinite;
}

@keyframes shimmer {
  to { transform: translateX(100%); }
}

/* Circle variant */
.skeleton--circle { border-radius: var(--radius-pill); }
Dark mode shimmer — adapts via CSS variables
/* Light: --ig-highlight-background = rgb(239,239,239), overlay = white-tinted
   Dark:  --ig-highlight-background = rgb(38,38,38),  overlay = white-tinted
   Both cases: the same ::after gradient works because it uses the
   --ig-primary-background token, which is 255, 255, 255 (light) and 12, 16, 20 (dark) */
[data-theme="dark"] .skeleton::after {
  background: linear-gradient(
    90deg,
    transparent 0%,
    rgba(var(--ig-elevated-background), 0.5) 50%,
    transparent 100%
  );
}

Skeleton patterns

Confirmed shapes per surface

Each skeleton pattern mirrors the exact layout of the resolved content. The slot dimensions are derived from the component specs documented in the Components section.

Feed post skeleton

Home feed, Profile grid

Avatar circle
Username line
Media block
Action row spacer
Caption line 1
Caption line 2

The media block is the dominant placeholder — match the known aspect ratio if available (4:5, 1:1, etc.) to avoid layout shift when the image loads.

Profile header skeleton

Profile page header

Avatar circle
Follower stat ×3
Display name
Bio line 1
Bio line 2
Action buttons

Follower/following/posts stats are always three equal-width blocks, centred. Reserve their exact space to prevent the header from reflowing when data arrives.

Story ring skeleton

Story tray, Profile highlights

Story avatar ×5
Username label ×5

Render 5 placeholder rings to match the typical visible count in the tray. The ring border (gradient when unread, grey when seen) should stay invisible during skeleton state.

Explore grid skeleton

Explore, Search results

3-col grid cells

Use a 3-column uniform grid with 2px gaps. Every cell is a square (1:1 ratio). Stagger the shimmer timing slightly between cells (20ms offset) so the sweep doesn't look synchronised.

Comment list skeleton

Comments sheet

Avatar circle
Username line
Comment body line 1
Comment body line 2

Render 4–6 skeleton comment rows to fill the visible area. Don't show the input composer until comments load — it avoids a jarring layout shift.

Timing

Animation timing rules

The shimmer duration and stagger are critical to the perception of responsiveness — too fast looks broken, too slow feels like a bug.

Shimmer duration

1.6s

Full sweep cycle. Slower than 2s feels broken; faster than 1.2s looks jittery.

Shimmer easing

linear

The gradient itself creates the ease effect — the animation timing function is always linear.

Stagger between rows

20–40ms

Offset each row's animation-delay slightly so rows don't sweep in a synchronised wave.

Spinner duration

1s

One full rotation per second. Instagram's spinner uses linear timing, not ease-in-out.

Spinner size

20–24px

Matches --icon-size-md or --icon-size-lg. Use 20px inline; 24px for full-screen waits.

Min visible time

300ms

Show the loading state for at least 300ms — an instant flash of skeleton followed by content is more jarring than no skeleton at all.

Do

  • Match skeleton shapes to resolved content — A 4:5 image skeleton prevents layout shift. A generic grey rectangle that resizes when the image loads creates a jarring jump.
  • Show stale content when available — If the user has seen this content before, show the cached version and refresh in the background. Skeletons are for first load.
  • Use --ig-highlight-background for skeleton fills — This token is defined for both light (239,239,239) and dark (38,38,38) mode — the skeleton automatically adapts.
  • Stagger row animation-delay — 20–40ms offset between rows prevents the synchronised wave that makes skeletons look low-effort.

Don’t

  • Don't use skeleton for actions — When a user taps Follow or Post, show a spinner inline in the button — not a skeleton. Skeletons are for initial content load, not interactions.
  • Don't fake progress — A progress bar that isn't based on real progress (upload %, download %) must be replaced with a spinner. Fake progress confuses users when it stalls.
  • Don't skeleton known-height empty states — If the surface has legitimately no content, show the empty state copy immediately — don't skeleton an empty list.
  • Don't animate in reduced-motion — The shimmer animation must be removed under prefers-reduced-motion. The static skeleton fill communicates loading without movement.
Reduced motion — required override
@media (prefers-reduced-motion: reduce) {
  .skeleton::after {
    animation: none;
    /* Static skeleton still visible — just no sweep */
  }
}