UI kit
RagSkeleton
DisplayReact · Next.jsLoading placeholders that match the shape of a chat reply, search results, or a streamed answer. Render it while the pipeline request is in flight to avoid layout shift.
<RagSkeleton />Live preview
answer
chat
search
Installation
No dependencies to install — just React + Tailwind CSS. It's a presentational component (no data fetching), so the same source works unchanged in React and Next.js (App or Pages Router) — there's no separate variant. Copy it into your project (e.g. components/RagSkeleton.tsx) and import it.
Usage
usage.tsx
import { RagSkeleton } from "@/components/RagSkeleton";
export default function Example() {
return (
<RagSkeleton variant="chat" />
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "answer" | "chat" | "search" | "answer" | Which layout to mimic. |
lines | number | 3 | Number of shimmer lines (answer/search). |
Component source copy-paste ready
Presentational component — no data fetching, so the same source works unchanged in React and Next.js. There's no separate Next.js variant to copy.
RagSkeleton.tsx
"use client";
interface RagSkeletonProps {
/** Which layout to mimic while loading. Default: "answer". */
variant?: "answer" | "chat" | "search";
/** Number of shimmer lines (answer / search variants). Default: 3. */
lines?: number;
className?: string;
}
function Bar({ w = "100%" }: { w?: string }) {
return <div className="h-3 animate-pulse rounded bg-zinc-200 dark:bg-zinc-800" style={{ width: w }} />;
}
/**
* RagSkeleton — loading placeholders that match the shape of a chat reply,
* search results, or a streamed answer. Render it while the pipeline request
* is in flight to avoid layout shift.
*/
export function RagSkeleton({ variant = "answer", lines = 3, className = "" }: RagSkeletonProps) {
if (variant === "chat") {
return (
<div className={`flex flex-col gap-3 ${className}`}>
<div className="flex justify-end">
<div className="h-9 w-2/3 animate-pulse rounded-2xl bg-zinc-200 dark:bg-zinc-800" />
</div>
<div className="flex justify-start">
<div className="h-16 w-5/6 animate-pulse rounded-2xl bg-zinc-100 dark:bg-zinc-800/60" />
</div>
</div>
);
}
if (variant === "search") {
return (
<div className={`flex flex-col gap-2 ${className}`}>
{Array.from({ length: lines }).map((_, i) => (
<div key={i} className="rounded-lg border border-zinc-200 p-3 dark:border-zinc-800">
<Bar w="40%" />
<div className="mt-2">
<Bar w="90%" />
</div>
</div>
))}
</div>
);
}
return (
<div className={`flex flex-col gap-2 ${className}`}>
{Array.from({ length: lines }).map((_, i) => (
<Bar key={i} w={`${90 - i * 12}%`} />
))}
</div>
);
}