{"slug":"loading-states-and-perceived-performance","title":"loading-states-and-perceived-performance","summary":"Manage user expectations during wait times with appropriate loading states — from simple spinners to complex skeleton screens and staggered animations. Perceived performance is often more important than actual load time. Use when designing data-heavy components, handling API call","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-09-03T16:18:14.872283Z","repo":{"url":"https://github.com/dembrandt/dembrandt-skills","stars":64,"forks":10,"license":"MIT","updatedAt":"2026-09-29T18:02:17Z"},"bodyHtml":"<hr>\n<h2>name: loading-states-and-perceived-performance\ndescription: Manage user expectations during wait times with appropriate loading states — from simple spinners to complex skeleton screens and staggered animations. Perceived performance is often more important than actual load time. Use when designing data-heavy components, handling API calls, building hero sections, or improving the feel of a slow interface.\nmetadata:\npriority: 7\npathPatterns:\n- \"components/<strong>\"\n- \"src/components/</strong>\"\n- \"<strong>/*.tsx\"\n- \"</strong>/<em>.jsx\"\n- \"**/</em>.css\"\n- \"<strong>/*.scss\"\n- \"design-system/</strong>\"\npromptSignals:\nphrases:\n- \"loading state\"\n- \"spinner\"\n- \"skeleton screen\"\n- \"skeleton loader\"\n- \"perceived performance\"\n- \"loading animation\"\n- \"shimmer effect\"\n- \"staggered loading\"\n- \"prefetch\"\n- \"prioritise loading\"\n- \"progressive loading\"\n- \"lazy load data\"\nretrieval:\naliases:\n- loading states\n- skeleton loaders\n- spinners\n- perceived performance\n- shimmy\n- glimmer\n- prefetching\n- priority loading\n- progressive data loading\nintents:\n- design a loading state\n- add a skeleton screen\n- improve perceived performance\n- choose between spinner and skeleton\n- handle slow data loading\n- adding delight to the wait\n- load the most important content first\n- prefetch the likely next step\nexamples:\n- what loading state should this card use\n- add a skeleton loader for this list\n- make the page feel faster while loading\n- design a spinner for this button\n- load the key content first then stream the rest\n- prefetch the next page so it feels instant</h2>\n<h1>Loading States and Perceived Performance</h1>\n<p>Users don't mind waiting as much if they understand <em>what</em> they are waiting for and <em>how much</em> progress is being made. Perceived performance is the design work of making a system feel faster than it actually is.</p>\n<hr>\n<h2>Choosing the Right Loading State</h2>\n<table>\n<thead>\n<tr>\n<th>Wait Duration</th>\n<th>Best Pattern</th>\n<th>Use for</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>Short (&lt; 1s)</strong></td>\n<td><strong>Inline Spinner / Loader</strong></td>\n<td>Button actions, small updates, quick data fetches</td>\n</tr>\n<tr>\n<td><strong>Medium (1s – 3s)</strong></td>\n<td><strong>Skeleton Screen</strong></td>\n<td>Cards, lists, dashboards, profile pages</td>\n</tr>\n<tr>\n<td><strong>Long (&gt; 3s)</strong></td>\n<td><strong>Determinate Progress Bar</strong></td>\n<td>File uploads, complex exports, heavy processing</td>\n</tr>\n<tr>\n<td><strong>Full Page</strong></td>\n<td><strong>Staggered Entry / Animated Sections</strong></td>\n<td>Initial app load, hero sections, immersive transitions</td>\n</tr>\n</tbody>\n</table>\n<hr>\n<h2>Simple Cases: Spinners and Loaders</h2>\n<p>Use spinners for small, contained actions where the layout doesn't change significantly.</p>\n<ul>\n<li><strong>Button Spinners:</strong> Replace button text or sit alongside it. The button should enter a <code>disabled</code> state to prevent double-submissions.</li>\n<li><strong>Micro-Loaders:</strong> A small 16–24px circle for inline updates (e.g., saving a single field).</li>\n<li><strong>Animation Tip:</strong> A \"spring-loaded\" rotation (easing in and out) feels more premium than a constant linear rotation.</li>\n</ul>\n<pre><code>@keyframes spin {\n  0%   { transform: rotate(0deg); }\n  100% { transform: rotate(360deg); }\n}\n.spinner {\n  animation: spin 800ms cubic-bezier(0.4, 0, 0.2, 1) infinite;\n}\n</code></pre>\n<hr>\n<h2>Skeleton Screens (Glimmer/Shimmer)</h2>\n<p>Skeleton screens provide a visual placeholder that mimics the layout of the final content. This reduces \"layout shift\" (CLS) and signals to the user exactly where the content will appear.</p>\n<h3>The Shimmer Effect</h3>\n<p>A subtle, moving gradient that travels across the skeleton elements.</p>\n<pre><code>.skeleton {\n  background: var(--color-grey-100);\n  background-image: linear-gradient(\n    90deg,\n    rgba(255, 255, 255, 0) 0%,\n    rgba(255, 255, 255, 0.5) 50%,\n    rgba(255, 255, 255, 0) 100%\n  );\n  background-size: 200% 100%;\n  animation: shimmer 1.5s infinite;\n}\n\n@keyframes shimmer {\n  0%   { background-position: -200% 0; }\n  100% { background-position: 200% 0; }\n}\n</code></pre>\n<h3>Rules for Skeletons</h3>\n<ul>\n<li><strong>Match the shape:</strong> If the final content is a round avatar, use a round skeleton. If it's a 2-line heading, use two bars of varying widths.</li>\n<li><strong>Stay Recessive:</strong> Skeletons should use your most subtle grey (<code>--color-grey-100</code> or <code>grey-50</code>). They should not draw focus.</li>\n<li><strong>Fade into Reality:</strong> When data arrives, fade the actual content in over the skeleton (150–200ms) rather than snapping.</li>\n</ul>\n<hr>\n<h2>Fully Animated Sections</h2>\n<p>For major page transitions or initial loads, use a coordinated animation strategy.</p>\n<h3>Staggered Entry (Cascading)</h3>\n<p>Instead of the whole page appearing at once, animate sections in a sequence. This guides the user's eye from the most important content (hero) down to secondary areas.</p>\n<pre><code>.section {\n  opacity: 0;\n  transform: translateY(10px);\n  animation: slide-up 400ms ease-out forwards;\n}\n/* Stagger by index */\n.section:nth-child(1) { animation-delay: 100ms; }\n.section:nth-child(2) { animation-delay: 200ms; }\n.section:nth-child(3) { animation-delay: 300ms; }\n\n@keyframes slide-up {\n  to { opacity: 1; transform: translateY(0); }\n}\n</code></pre>\n<h3>Hero Section \"Bloom\"</h3>\n<p>For hero sections, you might use a more complex animation:</p>\n<ol>\n<li><strong>Background image</strong> fades in slowly.</li>\n<li><strong>Heading</strong> slides in with a slight overshoot (spring).</li>\n<li><strong>CTA button</strong> appears last with a crisp fade-in or subtle color transition.</li>\n</ol>\n<hr>\n<h2>Load in Priority Order — and Prefetch What's Next</h2>\n<p>Don't wait for everything before showing anything. Load in the order of <strong>value to the user</strong>, so the thing they came for appears first and the rest fills in around it. This is both a perceived-performance win and a code-efficiency one: you fetch and render less up front.</p>\n<ul>\n<li><strong>First, the highest-value content</strong> — the key figure, the primary record, the above-the-fold answer. Render it the moment it's ready.</li>\n<li><strong>Then the next tier, then the next</strong> — secondary panels, related lists, and below-the-fold sections stream in behind it (skeletons hold their space so nothing shifts — see the skeleton section above).</li>\n<li><strong>Fetch only what the current view needs.</strong> Defer data for tabs, drawers, and off-screen sections until they're opened, rather than loading the whole page's worth of data at once.</li>\n</ul>\n<p><strong>Prefetch the predictable next step</strong> — next page, a hovered row's detail, the next wizard step — in the background so it's instant. Don't speculatively load everything; only where there's an obvious next move.</p>\n<h2>Advanced: Optimistic UI</h2>\n<p>The fastest UI is one that doesn't wait for the server at all.</p>\n<ul>\n<li><strong>The Pattern:</strong> Update the UI immediately assuming the server call will succeed. If it fails, roll back and show an error.</li>\n<li><strong>Use for:</strong> Liking a post, toggling a switch, renaming a folder, deleting a message.</li>\n<li><strong>Benefit:</strong> Instant gratification for the user, making the app feel \"lightning fast.\"</li>\n</ul>\n<hr>\n<h2>Adding Delight to the Wait</h2>\n<p>Loading doesn't have to be a neutral experience. For waits longer than 2 seconds, consider adding brand personality and \"delight\" to keep the user engaged.</p>\n<h3>Brand-Aligned Micro-copy</h3>\n<p>Replace generic \"Loading...\" text with wording that reflects the brand's voice.</p>\n<ul>\n<li><strong>Technical:</strong> \"Compiling data...\", \"Syncing with cloud...\"</li>\n<li><strong>Playful:</strong> \"Gathering pixels...\", \"Brewing your dashboard...\", \"Almost there!\"</li>\n<li><strong>Professional:</strong> \"Preparing your report...\", \"Verifying details...\"</li>\n</ul>\n<h3>Branded Animations (Lottie/SVG)</h3>\n<p>For significant loading moments (initial app boot, complex data processing), replace the standard spinner with a small, brand-specific animation.</p>\n<ul>\n<li>A designer's tool might show a pencil drawing a line.</li>\n<li>A fitness app might show a pulsing heart or a moving runner icon.</li>\n<li>A financial tool might show coins stacking or a chart line moving upward.</li>\n</ul>\n<h3>Progressive Storytelling</h3>\n<p>If a wait is consistently long (3s+), use the loading area to tell a small story or provide value:</p>\n<ul>\n<li><strong>Tips &amp; Tricks:</strong> \"Did you know you can use Ctrl+K to search?\"</li>\n<li><strong>Process Transparency:</strong> Show what the system is doing: \"Checking database...\" → \"Optimising results...\" → \"Finalising view...\"</li>\n</ul>\n<h3>Visual Transitions (Arrival)</h3>\n<p>When transitioning from a loading state to content, use a crisp fade-in (150ms) to make the arrival feel like a reward. Avoid scaling the incoming content, as it can cause layout instability.</p>\n<hr>\n<h2>Review Checklist</h2>\n<ul>\n<li><input disabled=\"disabled\" type=\"checkbox\"> Is the loading state appropriate for the expected wait duration (spinner vs skeleton)?</li>\n<li><input disabled=\"disabled\" type=\"checkbox\"> Does the skeleton screen match the physical layout of the incoming content?</li>\n<li><input disabled=\"disabled\" type=\"checkbox\"> Is there a subtle shimmer animation on skeletons to signal \"active loading\"?</li>\n<li><input disabled=\"disabled\" type=\"checkbox\"> Are buttons disabled during loading to prevent duplicate actions?</li>\n<li><input disabled=\"disabled\" type=\"checkbox\"> Does content fade in over skeletons (150–200ms) rather than blinking into existence?</li>\n<li><input disabled=\"disabled\" type=\"checkbox\"> For full-page loads, is a staggered entry used to guide the eye?</li>\n<li><input disabled=\"disabled\" type=\"checkbox\"> Is <code>prefers-reduced-motion</code> respected for all loading animations?</li>\n<li><input disabled=\"disabled\" type=\"checkbox\"> In \"Optimistic UI\" moments, is there a clear rollback path if the action fails?</li>\n<li><input disabled=\"disabled\" type=\"checkbox\"> Does content load in priority order (highest-value first, rest streaming in), fetching only what the current view needs rather than everything up front?</li>\n<li><input disabled=\"disabled\" type=\"checkbox\"> Where the next step is predictable, is it prefetched so it feels instant — without speculatively loading everything?</li>\n</ul>\n<h2>Common Anti-Patterns</h2>\n<table>\n<thead>\n<tr>\n<th>Anti-pattern</th>\n<th>Problem</th>\n<th>Fix</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>A global spinner that blocks the whole app</td>\n<td>High frustration, user cannot browse other areas</td>\n<td>Use contextual loaders or skeletons</td>\n</tr>\n<tr>\n<td>Skeletons that don't match the final layout</td>\n<td>Massive layout shift (CLS) when data arrives</td>\n<td>Match shapes and sizes exactly</td>\n</tr>\n<tr>\n<td>Too many spinners on one page</td>\n<td>Visual noise, feels like the whole app is broken</td>\n<td>Group loading states into a single container skeleton</td>\n</tr>\n<tr>\n<td>Faster-than-light skeletons</td>\n<td>Shimmer animation that is too fast or high-contrast</td>\n<td>Keep shimmer slow (1.5s+) and very subtle</td>\n</tr>\n</tbody>\n</table>\n","files":[{"path":"SKILL.md","sizeBytes":12011,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-09-23T13:50:42.995084Z","sha256":"F08782859164BD2E8559F9AB6E90545930DB8FED9492594609960B57B36C5822","sizeBytes":5252},"review":null,"source":{"repositoryUrl":"https://github.com/dembrandt/dembrandt-skills","path":"skills/loading-states-and-perceived-performance","license":"MIT","commit":"709f1696be1118dc9c837a5a60b75c870179eef8","subtreeSha":"64ED12D7BD85D2112BB0620C082F248A6F2E619B5AC277FCCA8BEA68EDAB72D6","lastSyncedAt":"2026-09-30T15:23:27.290693Z"},"reviewedAt":"2026-09-23T13:51:09.974906Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/dembrandt/dembrandt-skills/tree/main/skills/loading-states-and-perceived-performance"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install dembrandt-dembrandt-skills@llmmart"},{"target":"git","command":"git clone https://github.com/dembrandt/dembrandt-skills.git"}]}