stream/ui
Browse the library

Skeleton

Placeholder shapes for content that's loading, sized with classes and pulsing only when motion is allowed.

stableSkeleton.tsx

Examples

Profile row

skeleton/basic.tsx
import { Skeleton } from "@streamuilib/react";

export default function SkeletonBasic() {
  return (
    <div role="status" aria-busy="true" aria-label="Loading profile" className="flex w-full max-w-xs items-center gap-4">
      <Skeleton className="size-12 rounded-full" />
      <div className="flex flex-1 flex-col gap-2">
        <Skeleton className="h-4 w-3/4" />
        <Skeleton className="h-3 w-1/2" />
      </div>
    </div>
  );
}

Loading card

skeleton/card.tsx
import { Card, CardContent, CardFooter, CardHeader, Skeleton } from "@streamuilib/react";

export default function SkeletonCard() {
  return (
    <Card role="status" aria-busy="true" aria-label="Loading plan" className="w-full max-w-sm">
      <CardHeader>
        <Skeleton className="h-5 w-1/3" />
        <Skeleton className="h-3 w-2/3" />
      </CardHeader>
      <CardContent className="flex flex-col gap-2">
        <Skeleton className="h-8 w-1/2" />
        <Skeleton className="h-3 w-full" />
        <Skeleton className="h-3 w-5/6" />
      </CardContent>
      <CardFooter>
        <Skeleton className="h-11 w-full" />
      </CardFooter>
    </Card>
  );
}

Installation

bun add @streamuilib/react
usage.tsx
import { Skeleton } from "@streamuilib/react";

<div role="status" aria-busy="true" aria-label="Loading">
  <Skeleton className="h-4 w-40" />
</div>

API

PropTypeDefaultDescription
classNamestring—Size and shape, e.g. h-4 w-40 or size-12 rounded-full.
...propsComponentProps<"div">—Native div attributes.

Accessibility

  • Each skeleton is aria-hidden; label the loading region instead (role="status", aria-busy, aria-label) so screen readers hear one message.
  • The pulse only runs when the user allows motion.

Source

packages/react/src/Skeleton.tsx
import type { ComponentProps } from "react";
import { cn } from "./cn";

/**
 * A placeholder block for content that's loading. Size it with classes
 * (`h-4 w-40`, `size-12 rounded-full`). It's hidden from screen readers;
 * mark the loading region with `aria-busy` and give it a label instead.
 */
export function Skeleton({ className, ...props }: ComponentProps<"div">) {
  return (
    <div
      aria-hidden
      className={cn("border-2 border-ink-faint bg-paper-sunken motion-safe:animate-pulse", className)}
      {...props}
    />
  );
}