Loading overlay
Covers one region with a spinner while it loads, never the whole page.
npx shadcn@latest add https://ui.goodtabs.co/r/loading-overlay.json
With the @goodtabs namespace in components.json: npx shadcn@latest add @goodtabs/loading-overlay
Deploys
- main, 2 minutes ago
- fix-nav, 1 hour ago
- release-14, yesterday
Loading
A card covered while its list loads.
Refresh
Build report
- Bundle size
- 182 kB
- First paint
- 0.8 s
- Layout shift
- 0.01
A button toggles the overlay on a report for 1.5 seconds.
Usage
import { LoadingOverlay } from "@/components/ui/loading-overlay"
<LoadingOverlay loading={isPending} label="Refreshing">
<ReportTable />
</LoadingOverlay>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| loading | boolean | false | Shows the overlay, sets aria-busy and makes the content inert. |
| label | string | "Loading" | Status text announced and shown beside the spinner. |
Source
loading-overlay/loading-overlay.tsx
import * as React from "react";
import { Loader2Icon } from "lucide-react";
import { cn } from "@/lib/utils";
type LoadingOverlayProps = React.ComponentProps<"div"> & {
/** Covers the region and marks it busy. */
loading?: boolean;
/** Short status text next to the spinner, for example "Loading results". */
label?: string;
};
/**
* Covers one region while it loads. The wrapper is aria-busy and the content
* underneath is inert, so focus and clicks cannot reach it. It never covers
* more than its own box, so the rest of the page keeps working.
*/
export function LoadingOverlay({
loading = false,
label = "Loading",
className,
children,
...props
}: LoadingOverlayProps) {
return (
<div
data-slot="loading-overlay"
data-loading={loading || undefined}
aria-busy={loading}
className={cn("relative", className)}
{...props}
>
<div inert={loading} className="contents">
{children}
</div>
{loading && (
<div
role="status"
className="absolute inset-0 z-10 flex items-center justify-center gap-2 rounded-[inherit] bg-background/60 text-sm text-foreground backdrop-blur-[1px]"
>
<Loader2Icon className="size-4 animate-spin motion-reduce:animate-none" aria-hidden="true" />
<span>{label}</span>
</div>
)}
</div>
);
}
For AI agents
Hand your agent this page, the markdown at /components/loading-overlay/prompt.md, or the Copy AI prompt button above.
When to use
- A panel, table or card refetches and the rest of the page should stay usable
- Keeping stale content visible underneath while new data arrives
- Any region whose size you do not want to change while loading
When not to use
- First load of a list, where a skeleton shows the shape better
- Blocking the whole page, which this piece deliberately cannot do
Agent notes
- The overlay is absolute inside the wrapper, so the wrapper is relative and sized by its children
- While loading the content is inert: no focus, no clicks, no find in page targets
- Pass rounded and border classes on the wrapper; the overlay inherits its radius
- The spinner stops under reduced motion and the label still shows