Slow Network Bar
A 4px indeterminate progress bar that surfaces after a 3 s threshold to signal slow or stalled network requests — without blocking interaction.
Interactive Demo
Click "Export Report" to start a simulated slow request. The bar appears after 3 s and resolves when the operation completes.
Current state: hidden
Visual States
Installation
npx visor add slow-network-barThis copies two files into your project:
components/ui/slow-network-bar/slow-network-bar.tsx— the component + hookcomponents/ui/slow-network-bar/slow-network-bar.module.css— the styles
Usage
With useSlowRequest (recommended)
The useSlowRequest hook manages the threshold timer automatically. Call trigger() when a request starts and resolve() when it completes (in a finally block so errors also dismiss the bar).
import {
SlowNetworkBar,
useSlowRequest,
} from '@/components/ui/slow-network-bar/slow-network-bar';
export function ExportButton() {
const { state, trigger, resolve } = useSlowRequest(3000);
const handleExport = async () => {
trigger();
try {
await exportReport();
} finally {
resolve();
}
};
return (
<>
<SlowNetworkBar state={state} label="Generating export, please wait…" />
<button onClick={handleExport} disabled={state !== 'hidden'}>
Export Report
</button>
</>
);
}Controlled (manual state)
Drive the bar directly when you manage request state yourself (e.g., React Query, SWR, or Redux).
import { SlowNetworkBar } from '@/components/ui/slow-network-bar/slow-network-bar';
import type { SlowNetworkBarState } from '@/components/ui/slow-network-bar/slow-network-bar';
function Dashboard({ isLoading }: { isLoading: boolean }) {
const barState: SlowNetworkBarState = isLoading ? 'visible' : 'hidden';
return (
<>
<SlowNetworkBar state={barState} />
{/* page content */}
</>
);
}Composition Rules
- Never alongside Skeleton — both communicate loading. Choose one per content zone;
SlowNetworkBaris for requests that exceed the threshold;Skeletonis for initial data loading. - On error, dismiss immediately — set
stateto"hidden"(or callreset()) and let the error pattern take over. The bar must never linger after a failure. - Non-blocking — the bar does not prevent interaction. Users can cancel, navigate, or take other actions while it is visible.
- Position below navigation — the bar renders at the top of the content zone, immediately below the nav bar, using
position: relative; overflow: hiddenon its container.
useSlowRequest API
const { state, trigger, resolve, reset } = useSlowRequest(threshold?: number);| Name | Type | Description |
|---|---|---|
state | SlowNetworkBarState | Current bar state — pass to <SlowNetworkBar state={state} />. |
trigger() | () => void | Start the threshold timer. Call when a request begins. |
resolve() | () => void | Signal completion. If the bar is visible, transitions to resolving (sweep + fade). If the request resolved before the threshold, stays hidden. |
reset() | () => void | Return to hidden immediately — use on cancel or unmount. |
threshold | number | Milliseconds before the bar appears. Default 3000. |
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
state | 'hidden' | 'visible' | 'resolving' | 'hidden' | Visibility state. 'hidden' — not rendered; 'visible' — indeterminate animation active; 'resolving' — completes a full sweep then fades out (800ms). Pair with useSlowRequest for automatic threshold management. |
label | string | 'Loading, please wait…' | Accessible label announced to screen readers when the bar becomes visible. Override to describe the specific operation (e.g. "Uploading file, please wait…"). |
The component also accepts all standard <div> HTML attributes.
Accessibility
- Renders with
role="progressbar",aria-valuemin={0}, andaria-valuemax={100}. aria-busyistruewhilestateis"visible"or"resolving", andfalsewhen"hidden".aria-labeldefaults to"Loading, please wait…"— override with thelabelprop to describe the specific operation.prefers-reduced-motion: the indeterminate sweep animation pauses and the bar renders at full width as a static indicator; the fade-out is instant.
Customization
After copying the component, you own it completely. Common customizations:
// Custom threshold (5 s for upload-heavy operations)
const { state, trigger, resolve } = useSlowRequest(5000);
// Custom accessible label
<SlowNetworkBar
state={state}
label="Uploading file, please wait…"
/>
// Add to a layout so it appears on every slow page transition
export function AppLayout({ children }) {
const { state, trigger, resolve } = useSlowRequest(3000);
// wire trigger/resolve to your router events
return (
<>
<nav>…</nav>
<SlowNetworkBar state={state} />
<main>{children}</main>
</>
);
}Offline Banner
App-shell banner for network connectivity loss. Non-blocking sticky bar below nav with offline, reconnecting, and restored states.
Success Feedback
App-wide success/transition feedback pattern. useSuccessToast() fires an auto-dismissing success toast; SuccessLiveRegion provides an explicit a11y live region for screen readers.