Loading swap
FreeSwaps a loading placeholder for its content without a jump: the content fades in while the height eases once.
pnpm dlx shadcn@latest add https://caliperui.site/r/loading-swap.jsonWhen to use #
Use it for
- Panels and cards that load after the page.
- Sections whose final height differs from the skeleton.
Skip it when
- Whole pages; show the page frame at once instead.
Installation #
pnpm dlx shadcn@latest add https://caliperui.site/r/loading-swap.json
Adds its 2 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.
Usage #
This is the code behind the preview above.
import { useEffect, useState } from "react" import { Button } from "@/components/ui/button" import { LoadingSwap } from "@/components/ui/loading-swap" import { Skeleton } from "@/components/ui/skeleton" export function LoadingSwapDemo() { const [loading, setLoading] = useState(true); useEffect(() => { if (!loading) return; const id = setTimeout(() => setLoading(false), 1400); return () => clearTimeout(id); }, [loading]); return ( <div className="grid w-full max-w-sm gap-3"> <div className="rounded-[12px] p-4 shadow-[inset_0_0_0_1px_var(--border)]"> <LoadingSwap loading={loading} label="Loading the account summary" placeholder={<Skeleton lines={2} avatar label="Loading" />}> <div className="grid gap-2 text-sm"> <p className="font-medium">Ostra Freight</p> <p className="text-muted-foreground">Four entities, two currencies. Renewal on 30 October; security review is the last open item before signature.</p> <p className="text-muted-foreground">Owner Maya Chen · ARR €48,000 · Health: good</p> </div> </LoadingSwap> </div> <Button size="sm" variant="secondary" className="w-fit" onClick={() => setLoading(true)} disabled={loading}>Reload</Button> </div> ); }
API reference #
LoadingSwap
component
Also accepts the native props of its root element and a ref.
Accessibility #
It exposes status semantics, with aria-busy and aria-hidden kept in step with state. Changes that matter are announced through a live region.
No custom key handling; Tab and the native activation keys do the work.
Motion #
It declares no animation in its component source. Any transition comes from its stylesheet, which uses the duration tokens that collapse to instant under prefers-reduced-motion.