useDelayedLoading
Shows a loading state only when work is actually slow, then keeps it up long enough that it never flickers.
pnpm dlx shadcn@latest add https://hextaui.com/r/use-delayed-loading.jsonAdds the hook and anything it depends on to your project.
Copy and paste the following code into your project.
hooks/use-delayed-loading.ts Update the import paths to match your project setup.
Pass the raw loading flag and render from the boolean it returns. Most requests on a warm connection finish in under 150ms. Showing a spinner for those is worse than showing nothing: it flashes for a frame or two and reads as a glitch, not as progress.
The hook applies two rules. It waits for delay before showing anything, so work that finishes sooner never shows a loading state. Once the indicator is visible, it stays for at least minDuration, so it can't appear and vanish within a few frames.
| Work takes | Description |
|---|---|
80ms | Nothing is shown. |
250ms | Shown at 150ms and held until 550ms, the 400ms minimum. |
900ms | Shown at 150ms and hidden as soon as work ends. |
The 400ms minimum is long enough to register as a deliberate state and short enough not to slow anyone down.
- If
loadingturns back on while the indicator is still visible, it simply stays visible. There's no hide and show again. - Timers are cleared when the inputs change or the component unmounts, so nothing updates state after it's gone.
- On the server and during the first render it returns
false, so it never adds a hydration mismatch.
Skeletons
Skeletons replace content, so a flash is even more jarring than with a spinner. Here the first load is slow and shows the skeleton. Later loads come from a cache and never do.
- Ada Lovelace
- Grace Hopper
- Alan Turing
Raise delay for indicators that cover a lot of the screen, like skeletons or overlays. Lower it toward 0 for actions where any wait needs acknowledging, like a payment. Keep minDuration above roughly 300ms.
<Spinner loading={...} />and<Button loading>already use these timings. Reach for the hook when you render something else.- Keep the space the indicator will take, as the examples do, so the layout doesn't shift when it appears.
- Pair it with an
aria-busyor a status message. The hook only decides what to show visually.
| Prop | Type | Default |
|---|---|---|
loadingWhether the work is in progress right now. | boolean | – |
options.delayMilliseconds to wait before showing the loading state. | number | 150 |
options.minDurationMinimum milliseconds the loading state stays visible once shown. | number | 400 |
| Returns | Description |
|---|---|
boolean | Whether to show the loading state. Always false on the server. |
Spinner through its loading prop.