Skeleton Dashboard — Free HTML CSS Loading Snippet

Skeleton Dashboard · Loaders · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Shimmer: background-position keyframe on linear-gradient — GPU compositor, no layout/paint
background-size: 800px — gradient twice element width for visible sweep
Staggered row delays: animation-delay:var(--d) per list item and table row
Chart bars: inline height% creates naturally varied bar proportions
Stats grid: same CSS grid as real dashboard — no layout shift on load
Separate sk-line sizes: short/medium/wide/bold for text hierarchy matching
All skeleton elements: same border-radius and spacing as real components
GPU-safe: no transform, no opacity, only background-position change

About this UI Snippet

Skeleton Dashboard — Stats Grid, Chart Bars, List Items & Table Rows with Shimmer Animation

Screenshot of the Skeleton Dashboard snippet rendered live

A skeleton dashboard loading state shows the structural layout of a dashboard before data arrives — communicating "content is coming" while giving users an accurate preview of the page structure. This is significantly better UX than a full-page spinner because users can see the layout, start scanning, and mentally prepare for the incoming data. This snippet provides a complete dashboard skeleton: a 4-column stats grid, a 2-column main row (chart bars + list items), and a table with header and rows — all with the standard GPU-optimised shimmer animation.

The shimmer animation

The shimmer uses background: linear-gradient(90deg, #f1f5f9 25%, #e9eef5 50%, #f1f5f9 75%) with background-size: 800px (twice the typical element width). A @keyframes animation shifts background-position from -400px to +400px, sweeping the lighter colour across the element. This runs on the GPU compositor via background-position change — no layout or paint recalculation triggered, achieving 60fps even with many skeleton elements.

Staggered animation delays

List items and table rows have style="--d:0s", "--d:0.06s", etc. The CSS animation-delay: var(--d, 0s) creates a subtle wave effect where items start their shimmer at slightly different times. This makes the skeleton feel more organic rather than all elements flashing in sync.

The chart bar skeleton

The bar chart skeleton uses inline style="height:60%" etc. to create naturally varied bar heights — mimicking the appearance of a real bar chart. The flex align-items: flex-end layout aligns all bars to the bottom, matching a standard bar chart baseline.

Matching the real layout

The skeleton uses the same CSS grid structure as the real dashboard (4-column stats, 2-column main row) — ensuring no layout shift when real data replaces the skeleton. This is the key principle of skeleton loading: the structure must exactly match the loaded state.

Transition to real content

The toggleLoad() function demonstrates a simple fade-in transition from skeleton to content. In production, replace the simulate button with an API fetch: show skeleton on mount, replace with real content when the fetch resolves.

Handling partial loading states

In a real dashboard, different sections may load at different speeds. Show each card's skeleton independently: when the stats API resolves, replace only the .stats-row skeletons. When the chart API resolves, replace the chart skeleton. Each card can have an independent isLoading state. This progressive loading approach shows data as it arrives rather than waiting for all APIs to complete before revealing anything.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You don't have to work out why background-position is the safe property to animate here on your own. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why animating background-position on an oversized linear-gradient stays on the compositor thread while animating width or opacity on the same elements would not, or how the --d custom property staggers the list-item and table-row shimmer starts. The same assistant can help optimize it, for instance checking whether toggleLoad's querySelectorAll pass over every skeleton element could be reduced when only one card's data has actually arrived. It is just as useful for extending the dashboard: ask it to let each card (stats, chart, list, table) load and swap independently as its own fetch resolves, add a Next.js loading.js wrapper, or generate a variable number of skeleton rows based on an expected result count. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a full-page "skeleton dashboard" loading state in plain HTML, CSS, and JavaScript — no library, no canvas.

Requirements:
- A dashboard layout with a 4-column stats grid (collapsing to 2 columns on narrow screens via a media query), a 2-column main row containing a chart card and a list card, and a full-width table card below — using the same CSS grid/flex structure the real, loaded dashboard would use.
- Every placeholder element (stat lines, chart bars, list items, table header cells, table rows) must share one shimmer technique: a linear-gradient background at least twice the element's width, animated purely by shifting background-position across the element in a keyframe — this must be the only property animated, so the effect runs on the compositor thread without triggering layout or paint.
- List items and table rows must each receive a slightly increasing animation-delay (via a CSS custom property set inline per element) so the shimmer starts in a subtle staggered wave down the list rather than all elements pulsing in perfect unison.
- The chart-bar skeletons must use varied inline heights (not all the same height) so the placeholder reads as a real bar chart's silhouette.
- A toggle function must simulate the loaded state: stop every skeleton element's shimmer animation, recolor them to look like finished content, and fade them in with a CSS opacity transition — demonstrating the same swap you'd trigger from a real fetch's .then() callback.
- Explain in a comment why background-position animation does not trigger layout or paint, unlike animating width, height, or top/left.

Step by step

How to Use

  1. 1
    Click "Simulate data loaded" to see the transitionThe shimmer animations stop and a fade-in effect suggests real content appearing. Click again to reload and see the skeleton state again.
  2. 2
    Match your real dashboard layoutThe skeleton must mirror your actual dashboard structure to avoid layout shift. Match the same grid columns, card heights, and padding as your real components.
  3. 3
    Show skeleton on data fetchRender the skeleton HTML on page load. When your API fetch resolves, replace the skeleton containers with real content: container.innerHTML = renderRealContent(data).
  4. 4
    Adjust shimmer speedUpdate the 1.6s animation duration on shimmer. Slower (2.4s) is more subtle and professional. Faster (0.8s) is more energetic. The speed applies to all skeleton elements simultaneously.
  5. 5
    Add more skeleton componentsDuplicate any skeleton card structure and add it to the dashboard. The shimmer animation applies automatically via CSS — no JavaScript changes needed. Match your actual component heights.
  6. 6
    Export in your formatClick "HTML" for a standalone file, "JSX" for a React component that accepts isLoading and renders skeleton or children, or "Tailwind" for a Tailwind CSS version.

Real-world uses

Common Use Cases

Analytics and metrics dashboard initial load state
Show the full dashboard skeleton while all API calls run in parallel (stats, chart data, recent activity, table). Replace each section independently as its API call resolves — users see data appearing progressively rather than waiting for everything to complete.
Admin panel and CRM dashboard loading
Admin dashboards built on a dashboard layout need instant perceived performance. The skeleton shows the familiar layout immediately on navigation, filling with real data within 200-500ms. Users recognise the structure and start planning their next action before data arrives.
Social feed and content listing loading states
Use the list item skeletons for social feeds, article lists, and any content grid. The staggered animation delays make the skeleton feel natural. Show 5-8 skeleton rows to match the typical first-page count.
React Suspense and Next.js loading.js integration
In Next.js App Router, place the skeleton component in app/dashboard/loading.js. Next.js automatically shows this component while the dashboard page component is loading. React Suspense uses a similar pattern: {isLoading ? <SkeletonDashboard /> : <Dashboard data={data} />}.
Study GPU-optimised CSS shimmer animation technique
The shimmer demonstrates the background-position animation technique that runs on the GPU compositor without triggering layout or paint recalculation. Understanding why background-position is compositor-safe (unlike width, height, or opacity changes) is fundamental to high-performance CSS animations.
E-commerce product listing and search results skeleton
Show skeleton cards while search results load. Each skeleton card matches the product card layout: image placeholder, title lines, price line. The skeleton communicates the number of results that will appear, reducing perceived wait time on search-heavy pages.

Got questions?

Frequently Asked Questions

CSS properties are divided into those that require layout, paint, or compositor-only. background-position changes how a background image is drawn on the compositor layer — it does not require recalculating layout (no element dimensions change) or full page repaint. The GPU compositor handles the visual update directly. In contrast, animating width, height, top, or left triggers layout recalculation on the main thread, which is expensive. The shimmer's background-position animation runs at 60fps even with 50 skeleton elements on screen.

The skeleton must use exactly the same CSS grid, flex, padding, margin, and height values as the real components. Set min-height on skeleton cards to match the real card height. If real cards have variable heights, set the skeleton to the average height or the minimum height. Test by replacing the skeleton HTML with real content and checking the Cumulative Layout Shift score in Chrome DevTools Lighthouse. Any score above 0.1 indicates a layout shift issue.

Use JavaScript to generate N skeleton items: const count = 5; const skeletons = Array.from({length: count}, (_,i) => <div class="sk-list-item" style="--d:${i*0.05}s"></div>).join(""); listContainer.innerHTML = skeletons. You can also match the expected count from a pagination API response: if the API says total=47 and page_size=10, show 10 skeleton items. This matches the number of real items that will appear.

In Next.js App Router: create app/dashboard/loading.js (or loading.tsx) and export the SkeletonDashboard component as default. Next.js automatically renders this file's export while the app/dashboard/page.js component is loading server-side data. For React Suspense: <Suspense fallback={<SkeletonDashboard />}><Dashboard /></Suspense>. The fallback renders until the async component inside Suspense resolves. Use React 18's Suspense with server components for the most seamless experience.