
Loading Indicators
- 1 installs
- 73.4k repo stars
- Updated June 18, 2026
- thedaviddias/frontendchecklist
Adds loading states with skeletons or spinners and ARIA attributes during async operations so users get immediate feedback.
About
A frontend-checklist rule for showing loading indicators during async operations. A developer uses it so users know something is happening and do not click repeatedly.
- Skeletons for content, spinners for actions
- Include ARIA attributes and avoid content jumps
Loading Indicators by the numbers
- 1 all-time installs (skills.sh)
- Ranked #1,912 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/thedaviddias/frontendchecklist --skill loading-indicatorsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 73.4k |
| Last updated | June 18, 2026 |
| Repository | thedaviddias/frontendchecklist ↗ |
What it does
Adds loading states with skeletons or spinners and ARIA attributes during async operations so users get immediate feedback.
Files
Show loading indicators
Loading indicators reassure users that something is happening—without feedback, users may think the page is broken or click repeatedly, degrading experience and causing duplicate actions.
Quick Reference
- Show immediate feedback for all async operations
- Use skeletons for content, spinners for actions
- Include ARIA attributes for screen reader accessibility
- Avoid content jumps when loading completes
Check
Verify that loading states are displayed during async operations with appropriate ARIA attributes for screen readers.
Fix
Implement loading spinners/progress bars with aria-busy, aria-live, and clear visual feedback for ongoing operations.
Explain
Explain how loading indicators improve perceived performance and accessibility by communicating system status to users.
Code Review
Review the routes, assets, and loading behavior that affect Show loading indicators. Flag exact files, requests, or rendering steps that add unnecessary network, CPU, or layout cost, and describe the measurement method used to confirm the issue.
---
For full implementation details, code examples, and framework-specific guidance, see references/rule.md.
Rule page: https://frontendchecklist.io/en/rules/performance/loading-indicators
Show loading indicators
Loading indicators provide feedback during asynchronous operations to keep users informed of progress.
Priority: high · Difficulty: beginner · Time: 15 min
--- Loading indicators keep users informed during asynchronous operations.
Code Examples
function Spinner({ size = 24 }: { size?: number }) {
return (
<svg
width={size}
height={size}
viewBox="0 0 24 24"
className="animate-spin"
role="status"
aria-label="Loading"
>
<circle
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
fill="none"
opacity="0.25"
/>
<path
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
/>
</svg>
)
}@keyframes spin {
to { transform: rotate(360deg); }
}
.animate-spin {
animation: spin 1s linear infinite;
}Why It Matters
Loading indicators reassure users that something is happening—without feedback, users may think the page is broken or click repeatedly, degrading experience and causing duplicate actions.
Types of Loading Indicators
| Type | Best For | Duration |
|---|---|---|
| Skeleton | Content loading | 1-5 seconds |
| Spinner | Button actions | 0.5-3 seconds |
| Progress bar | File uploads, long processes | 5+ seconds |
| Shimmer | Cards, lists | 1-3 seconds |
Skeleton Loading
function SkeletonCard() {
return (
<div className="card" aria-busy="true" aria-label="Loading content">
<div className="skeleton skeleton-image" />
<div className="skeleton skeleton-title" />
<div className="skeleton skeleton-text" />
<div className="skeleton skeleton-text" style={{ width: '60%' }} />
</div>
)
}
function CardList({ isLoading, items }) {
if (isLoading) {
return (
<div role="feed" aria-busy="true">
{[1, 2, 3].map(i => )}
</div>
)
}
return (
<div role="feed" aria-busy="false">
{items.map(item => )}
</div>
)
}.skeleton {
background: linear-gradient(
90deg,
#f0f0f0 25%,
#e0e0e0 50%,
#f0f0f0 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: 4px;
}
.skeleton-image {
height: 200px;
width: 100%;
}
.skeleton-title {
height: 24px;
width: 80%;
margin-top: 12px;
}
.skeleton-text {
height: 16px;
width: 100%;
margin-top: 8px;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}Button Loading State
interface ButtonProps {
children: React.ReactNode
loading?: boolean
onClick?: () => void
}
function Button({ children, loading, onClick }: ButtonProps) {
return (
<button
onClick={onClick}
disabled={loading}
aria-busy={loading}
className={`button ${loading ? 'button-loading' : ''}`}
>
{loading && }
<span className={loading ? 'sr-only' : ''}>{children}</span>
{loading && <span aria-live="polite">Processing...</span>}
</button>
)
}.button {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
}
.button-loading {
cursor: wait;
opacity: 0.8;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}Progress Bar
interface ProgressProps {
value: number // 0-100
label: string
}
function ProgressBar({ value, label }: ProgressProps) {
return (
<div
role="progressbar"
aria-valuenow={value}
aria-valuemin={0}
aria-valuemax={100}
aria-label={label}
>
<div className="progress-track">
<div
className="progress-fill"
style={{ width: `${value}%` }}
/>
</div>
<span className="progress-text">{value}% complete</span>
</div>
)
}React Suspense with Loading
function ProductPage({ id }: { id: string }) {
return (
<div>
}>
}>
</div>
)
}
// Loading state automatically shown while data fetches
async function ProductDetails({ id }: { id: string }) {
const product = await getProduct(id)
return <div>{product.name}</div>
}Full Page Loading
function PageLoader() {
return (
<div
className="page-loader"
role="alert"
aria-busy="true"
aria-label="Page loading"
>
<p aria-live="polite">Loading page...</p>
</div>
)
}Accessibility Requirements
// Always include ARIA attributes for screen readers
function AccessibleLoader({ isLoading, children }) {
return (
<div
aria-busy={isLoading}
aria-live="polite"
>
{isLoading ? (
<div role="status">
<span className="sr-only">Loading content, please wait</span>
</div>
) : (
children
)}
</div>
)
}Avoid Content Jumps
// Reserve space to prevent layout shift
function ContentWithLoader({ isLoading, content }) {
return (
<div style={{ minHeight: '200px' }}>
{isLoading ? : }
</div>
)
}Verification
Automated Checks
- Test with slow network (3G throttling)
Manual Checks
- Verify loading indicators appear immediately on action
- Check ARIA attributes with screen reader
- Verify no layout shift when content loads
- Check focus management after loading completes