A CSS skeleton loader is a content-shaped placeholder shown during data fetching to reduce perceived wait time and prevent layout shift. These 22 hand-coded skeletons cover the technique primer (React Suspense, Next.js loading UI, prefers-reduced-motion fallback, dark-mode via color-mix(), SVG blur-up, clamp() sizing, cross-fade, ARIA live-region, timing recipe, CLS-preserving min-height) plus production surfaces (avatar, article, table, stats, comments, media list, chip cluster). Every demo is scoped under .sk-NN, guards prefers-reduced-motion, and stays framework-agnostic.
17 pure CSS5 light JSPublished 10 new designs · Updated on
The pattern React devs actually ship: a <Suspense> boundary whose fallback is a skeleton with the same geometry as the component it stands in for. The demo puts the pending state and the resolved state side by side against a labelled component tree, so you can see that a good fallback is a tracing of the real layout — same avatar size, same line count, same card padding.
Published
02 / 22Pure CSSNEW
Next.js Loading UI Skeleton
Next.js App Router turns a whole route into a loading state: drop loading.tsx next to page.tsx and the framework wraps the segment in a Suspense boundary for you. This demo renders that route-level shell inside mock browser chrome — header, sidebar and main grid all skeletonised — with the file tree annotated so the placement is unambiguous.
Published
03 / 22Light JSNEW
Skeleton with prefers-reduced-motion Fallback
Shimmer is a repeating motion in a user's peripheral vision — for a vestibular-sensitive reader that is a symptom trigger, not a delight. This demo shows the animated skeleton and its prefers-reduced-motion fallback side by side, with an in-demo toggle so you can preview the reduced state without touching your OS settings.
Published
04 / 22Pure CSSNEW
Dark Mode Skeleton with color-mix Tokens
Stop shipping two skeleton palettes. color-mix(in oklab, currentColor 8%, transparent) derives the fill from whatever text colour the surface already inherits, so one class renders correctly on a near-black product surface and on white — no [data-theme] overrides for the bars themselves. Flip the toggle and watch identical markup re-tune itself.
Published
05 / 22Light JSNEW
SVG Placeholder Blur-Up Image Skeleton
The Medium / WordPress blur-up pattern, hand-rolled: a two-shape inline SVG blurred to blur(20px) sits inside an aspect-ratio box and cross-fades out when the real photograph decodes. Press Simulate slow load to replay the 1.2s reveal as many times as you need to get the easing right.
Published
06 / 22Pure CSSNEW
Content-Aware Skeleton Sizing with clamp
A skeleton line sized width: 320px is a promise your real paragraph will not keep. Sizing bars with clamp() and ch units makes them track the measure of the text that replaces them at every viewport — drag the resize handle on either panel and watch the fixed-width version break while the clamped one holds.
Published
07 / 22Light JSNEW
Skeleton to Content Cross-Fade Transition
The swap is the part users actually feel. Stack the skeleton and the real content in the same grid cell, cross-fade them over 200ms, and the resolved content arrives instead of snapping in — with @starting-style and transition-behavior: allow-discrete so the entry animates even though the element starts at display: none.
Published
08 / 22Light JSNEW
ARIA Live Region Loading Announcement
A skeleton is a purely visual promise — a screen reader user gets silence. This demo pairs the skeleton with a visually hidden aria-live="polite" region that says "Loading" on mount and "Content loaded" on resolve, shows the role="status" alternative side by side, and prints an announcement transcript so you can see exactly what assistive tech will read.
Published
09 / 22Light JSNEW
Skeleton Timing and Duration Recipe
800ms feels frantic, 2400ms feels broken, and 1400ms is the value GitHub, Linear and Notion all converged on. Three panels run those durations simultaneously so the difference is impossible to miss — then a live slider lets you dial any value from 600ms to 3000ms and compare linear against cubic-bezier(.4, 0, .6, 1).
Published
10 / 22Pure CSSNEW
Layout-Preserving Skeleton with min-height
A skeleton that is shorter than its content doesn't prevent layout shift — it schedules one. Both columns here load the same article; the left one lets the content push the footer down, the right one declares min-height and aspect-ratio so nothing below it ever moves. Flip the switch and watch the ruler.
Published
11 / 22Pure CSS
Classic Shimmer Skeleton Loader
The reference shimmer, written the canonical way: a three-stop linear-gradient at 200% width, moved with background-position. This is the version every tutorial teaches — so the demo also shows the compositor-friendly variant beside it and names the exact trade-off between them.
PublishedUpdated
12 / 22Pure CSS
Pulse Block Skeleton Loader
The cheapest credible loading animation there is: one opacity keyframe, 100% → 40% → 100%. No gradient, no extra layer, nothing to repaint — the compositor handles it alone, which is why it is the right default on low-end mobile and inside long virtualised lists.
PublishedUpdated
13 / 22Pure CSS
Avatar Profile Skeleton Loader
The pattern Slack, Discord and Twitter all show while a message resolves: a 48px circle plus a name line and a subtitle line. It looks trivial — the craft is in the details, which is why this demo puts the skeleton immediately above the resolved rows so you can check the circle diameter, the baseline gap and the two line widths against the real thing.
PublishedUpdated
14 / 22Pure CSS
Image Placeholder Skeleton Loader
A 16:9 media placeholder that reserves its box exactly, with a centred inline SVG so it reads as "image loading" rather than "something is broken". Distinct from the blur-up demo: no LQIP here, just the correct geometry, an icon, and a caption row that also reserves its two lines.
PublishedUpdated
15 / 22Pure CSS
Wave Sweep Skeleton Loader
A shimmer with a shaped leading edge. Instead of a straight-edged gradient, an inline SVG wave becomes the sweep's mask-image, so the highlight arrives as a soft crest rather than a bar wiping past — the softer, more expensive-feeling loading state used on premium product surfaces.
PublishedUpdated
16 / 22Pure CSS
Comment Thread Skeleton Loader
Threads load after the post, so the skeleton sits under real content and has to match its rhythm exactly. Three comment shells — avatar, name, meta, three text lines — plus one indented reply, all built from a real <ul> with the same indent and vertical spacing the resolved thread uses.
PublishedUpdated
17 / 22Pure CSS
Article Page Skeleton Loader
A full reading-view shell: hero block, title, byline row, then six paragraphs of bars at varying widths with a short final line in each — the Medium / Substack / Ghost pattern. The measure is capped in ch, so the placeholder occupies exactly the column the article will fill.
PublishedUpdated
18 / 22Pure CSS
Data Table Row Skeleton Loader
Header row real, body rows pending — the SaaS data-table pattern from Airtable, Notion and Retool. It stays a real <table> with a real <thead>, so column widths are already resolved and assistive tech still announces "table, 4 columns" while the data loads.
PublishedUpdated
19 / 22Pure CSS
Stat Tiles Dashboard Skeleton Loader
The dashboard hero row: four KPI tiles where the labels are real ("Users", "Revenue", "Sessions", "Conversion Rate") and only the numbers are pending. Keeping the labels means the user can read the dashboard's shape immediately — and the tile never resizes, because a tabular-figures number occupies a predictable box.
PublishedUpdated
20 / 22Pure CSS
Gradient Glow Skeleton Loader
For the surfaces users paid for: a rotating conic-gradient glow around the loading container, driven by a real animated angle via @property. Registering the custom property with syntax: '<angle>' is what makes it interpolate at all — without registration the browser treats it as a string and the rotation simply never happens.
PublishedUpdated
21 / 22Pure CSS
Media List Skeleton with Thumbnails
The queue pattern from YouTube, Spotify and every podcast app: five rows, each a 64px square thumbnail plus a title line and a metadata line, with a duration pill on the right. Rows are a real <ul>, the thumbnail is a fixed square, and the staggered sheen fills the list from the top.
PublishedUpdated
22 / 22Pure CSS
Chip Cluster Filter Skeleton Loader
Eight pill placeholders of deliberately uneven widths, wrapping like the real filter row they stand in for — the faceted-search pattern from every e-commerce and issue-tracker UI. Uneven widths are the entire point: equal pills read as a loading widget, uneven ones read as words you cannot make out yet.
PublishedUpdated
↗
Build your own
Tweak the exact look in our visual generators — no signup, instant copy-paste.
How do I use skeleton loaders with React Suspense and Next.js App Router loading.tsx?
React Suspense boundaries and Next.js App Router loading.tsx files are the two canonical places to render a skeleton loader in a 2026 React app. **React Suspense** wraps a component that may suspend on data fetching or lazy-loading and renders the fallback prop until the child resolves — the fallback is your skeleton component. Demo 01 (React Suspense Fallback Skeleton) ships the exact pattern. Wrap any component that fetches data or lazy-imports code in a Suspense boundary and pass a skeleton as the fallback. **Next.js loading.tsx** is the App Router file convention (introduced in Next 13) — place a loading.tsx next to a page.tsx inside any route segment and Next.js automatically wraps that segment in a Suspense boundary using your loading.tsx component as the fallback. Demo 02 (Next.js Loading UI Skeleton) shows the file-tree pattern with a mock dashboard skeleton. Both patterns benefit from a stable skeleton that matches the final content's dimensions so users don't see layout shift when data arrives — pair with Demo 10 (Layout-Preserving Skeleton with min-height) for CLS-safe transitions. For Vue 3 use a Suspense component with a fallback template slot; for SvelteKit use {#await} blocks; for Astro use client:visible islands with the skeleton in the SSR-rendered fallback slot. Every framework has the primitive; the CSS is portable.
How do I make a skeleton loader accessible for screen readers and WCAG 2.2 AA?
Skeleton loaders are decorative to sighted users but need explicit accessibility markup for assistive tech. Three-part recipe: (1) **role="status" OR aria-live="polite"** on the skeleton container so screen readers know a state change is happening. Add a visually-hidden Loading inside the container so the announcement has text to read (empty regions get skipped). Demo 08 (ARIA Live Region Loading Announcement) ships the full pattern with a visually-hidden pre-announcement and a real-content post-announcement swap. (2) **prefers-reduced-motion guard** on the shimmer or pulse animation per WCAG 2.2 SC 2.3.3 — replace the animation with a static muted background so the loading state stays visually distinct without triggering vestibular sensitivity. Demo 03 (Skeleton with prefers-reduced-motion Fallback) shows the exact @media rule and includes a preview toggle so devs can test both states. (3) **aria-busy="true"** on any interactive parent (button, form, list) that shouldn't be touched while loading — screen readers announce the busy state and defer focus. Set aria-busy="false" when content arrives. Beyond markup: the skeleton shimmer contrast must stay above 3:1 against the surrounding surface so low-vision users can perceive the loading state at all. Every demo in this collection ships all three pieces.
How do I prevent Cumulative Layout Shift (CLS) with skeleton loaders?
Skeletons only prevent CLS if they occupy the SAME space the real content will fill. Three techniques from Demo 10 (Layout-Preserving Skeleton with min-height): (1) **min-height** on the skeleton container matching the expected final content height — a card that will render ~180px tall should have min-height: 180px on the skeleton so surrounding elements don't shift when the real content arrives. (2) **aspect-ratio** on image placeholders — a hero image at 16:9 needs aspect-ratio: 16 / 9 on the skeleton so the browser reserves the ratio-correct box before the image loads. Demo 05 (SVG Placeholder Blur-Up Image Skeleton) pairs this with LQIP for the smoothest experience. (3) **clamp()** on text-line widths — the skeleton lines resize with viewport width matching what real text lines do, so wide-viewport → narrow-viewport transitions don't cause shift. Demo 06 (Content-Aware Skeleton Sizing with clamp) shows the recipe. Measure your real CLS score in Chrome DevTools Performance panel or the free PageSpeed Insights tool at https://pagespeed.web.dev — a CLS score under 0.1 passes Core Web Vitals; under 0.05 is Google's recommended target. Getting to 0.00 (perfect) requires all three techniques applied together. Every skeleton in this collection is authored with CLS in mind.
How do I use CSS color-mix() to build dark-mode-adaptive skeleton loaders?
Traditional skeleton loaders declare separate CSS custom properties for light and dark theme (--skeleton-bg-light: #e5e7eb; --skeleton-bg-dark: #374151;) then toggle via [data-theme]. Modern approach in Demo 04 (Dark Mode Skeleton with color-mix Tokens): use color-mix(in oklab, currentColor 8%, transparent) for the skeleton base and color-mix(in oklab, currentColor 14%, transparent) for the shimmer highlight — the skeleton adapts automatically to WHATEVER text colour is inherited from the parent, so dark theme (--text: #f5f5f5) yields a light-on-dark skeleton and light theme (--text: #0a0a0e) yields a dark-on-light skeleton with no theme-switch code. Uses the oklab colour space for perceptually-uniform mixing (Chrome 111+/Safari 16.4+/Firefox 113+). Wrap in @supports (color: color-mix(in oklab, red, blue)) for older-browser fallback that declares hex tokens directly. This is the pattern Vercel, Linear, Notion, and Superhuman use in production for their design-system skeletons. Every skeleton in this collection uses the color-mix() recipe so a single design-token change in --text reskins the whole set.
What is LQIP (Low Quality Image Placeholder) and how does the SVG blur-up skeleton work?
LQIP is a Medium/WordPress-popularised pattern for image loading: instead of a solid grey placeholder while the image downloads, show a tiny (100-500 byte) blurred SVG or Base64-encoded 32×18px thumbnail sized to fill the image's box, then fade in the full image when it arrives. Demo 05 (SVG Placeholder Blur-Up Image Skeleton) ships the exact recipe: (1) a small SVG (a few colour blobs or a solid gradient) sits in the image's aspect-ratio-locked box, blurred with filter: blur(20px) and transform: scale(1.05) to hide the blur edge, (2) the real image element sits over it with opacity: 0, (3) on the load event, JavaScript transitions the real image opacity to 1 over 400ms, (4) the LQIP layer removes itself after the transition. Combines the perceived speed of a skeleton (immediate visible box, no CLS) with the visual continuity of a real image (blurred preview matches the final colour palette). Every image-heavy site uses some LQIP variant now: Medium (their original implementation), Wordpress via the wp-content LQIP plugin, Next.js Image component with the blur placeholder prop, Astro Image component, Gatsby gatsby-plugin-image. Server-side generation of LQIP typically uses sharp or blurhash; the demo shows a hand-authored SVG version for zero-server deployment.
What's the right shimmer duration for a skeleton loader — 800ms, 1400ms, or 2400ms?
The sweet spot is 1200-1500ms per shimmer cycle. Faster (600-800ms) reads as frenetic and increases perceived stress (studies from the Nielsen Norman Group in 2019 and Apple's WWDC 2022 UI guidelines both flag sub-1000ms loading animations as anxiety-inducing). Slower (2400ms+) reads as laggy and feels like the app itself is slow. GitHub, Linear, Notion, Superhuman, and Stripe Dashboard all use 1400ms ±200ms for their production skeletons — measured in DevTools Animation tab. Demo 09 (Skeleton Timing and Duration Recipe) ships a live comparison at three durations with a slider so devs can tune to their own aesthetic. Beyond duration, the easing matters too — linear infinite is standard for shimmer (the sweep should feel like a metronome, not a heartbeat), while cubic-bezier(0.4, 0, 0.6, 1) works for pulse (opacity ramp). Wrap the whole animation in @media (prefers-reduced-motion: reduce) to disable it entirely for vestibular-sensitive users per WCAG 2.2 SC 2.3.3 (Demo 03 shows the exact pattern). Never use motion faster than 800ms in a loading state — it reads as broken.
How is CSS Skeleton Loaders different from CSS Shimmer Skeleton Cards, Loading Animations, and Spinner Designs?
codefronts.com ships four loading-state collections, each in a distinct lane to cover the full search-intent spectrum without cannibalisation. **CSS Skeleton Loaders (this collection)** is the FOUNDATIONAL TECHNIQUE PRIMER — 10 new demos teach modern loading-state patterns (React Suspense, Next.js loading UI, reduced-motion fallback, color-mix theming, blur-up images, clamp sizing, cross-fade, ARIA live-region, timing recipe, CLS-preserving min-height) and 12 reference skeleton surfaces (shimmer, pulse, avatar, image, wave, comment thread, article, table, stat tiles, gradient glow, media list, chip cluster). Start here to LEARN skeleton loaders. **CSS Shimmer Skeleton Cards** ships 15+ product-shape-specific variants (Instagram profile card, E-commerce product grid card, SaaS dashboard widget, Chat bubbles, Masonry gallery) for when you need a specific card design. **CSS Loading Animations** covers narrative loaders — Coffee Brewing, Vinyl Spin, Delivery Truck Route, Weather Radar Sweep — for onboarding and playful loading screens. **CSS Spinner Designs** ships spinner-only variants (Neon Arc, Dual Ring, Dot Chase, DNA Helix) for classic determinate loaders. If you're not sure which to pick: start with this collection to understand the technique, then move to the specialised collection for your specific component. Every demo across all four collections uses prefers-reduced-motion guard and role="status" announcement patterns.
Do these skeleton loaders need JavaScript, and can I integrate them with Tailwind CSS, React, Vue, Svelte, or Astro?
20 of 22 skeletons are pure CSS with zero JavaScript — all background-position shimmer, opacity pulse, and mask-image wave animations, keyframes, and CSS custom properties. Two demos ship under-25-line vanilla ES2024 JavaScript with zero dependencies: Demo 03 (Skeleton with prefers-reduced-motion Fallback) uses a toggle to preview both animation states, Demo 07 (Skeleton to Content Cross-Fade Transition) uses a reload handler to preview the crossfade repeatedly. Every animated demo includes @media (prefers-reduced-motion: reduce) disabling transitions. Framework portability: (1) **Tailwind CSS v3/v4** — use animate-pulse for the pulse recipe, bg-gradient-to-r from-transparent via-white/10 to-transparent for shimmer, arbitrary bg-[length:200%_100%] for the sweep, @theme { --color-skeleton-base: color-mix(...) } in v4 for token-driven palettes. (2) **React / Next.js App Router** — wrap the skeleton in a component and render as the fallback prop of a Suspense boundary or as the body of a loading.tsx file per Next 13+ App Router conventions. (3) **Vue 3 / Nuxt 4** — render inside a Suspense component's fallback template slot. (4) **Svelte 5 / SvelteKit** — wrap async data-loading in {#await} blocks with the skeleton as the pending branch. (5) **Astro** — use client:visible on interactive islands with the skeleton in the SSR-rendered slot; the pure-CSS skeletons work without any hydration. Copy the HTML markup, adapt to your framework's template syntax, ship — every demo is MIT licensed and free for commercial use with no attribution requirement.