Streaming Skeleton

The placeholder a host shows while a tool call's name or props are still unknown.

Primitive

Example: Streaming Skeleton

Raise the daily budget on the spring campaigns by 20%.

Generating…

This isn't an agent tool. The agent never calls it: the Renderer or your host renders GenUIPlaceholder in the gap between "a tool call started" and "we know which component it is", then swaps in the real component.

When to use

  • A tool call has started, but its name or props haven't arrived, so the real component can't render yet.
  • You know roughly what's coming (a table, a list, a card) and want the message to keep its place.

When not to use

  • The component is known. Render it with status="streaming": every GenUI component has its own loading state.
  • The agent is still writing text. Show the text as it streams instead.

How the host should use it

  • Replace it as soon as the tool name is known. The real component's streaming state takes over from there.
  • Pick the shape from the tool name when you can, and fall back to card.
  • Keep lines close to the expected size, so the layout doesn't jump when the component arrives.
  • Set activity only when you know the step (a search tool is running, the model is writing). Otherwise leave it out.

Shapes

ShapeSurfaceDefault linesLooks like
cardCard3A title, one large value, label/value pairs and footer buttons
tableFlush4A header row and data rows
listCard4A title and checkbox-style rows
textFlush3Paragraph lines, the last one shorter
mediaCard1A 16:9 block with caption lines

Activity orb

Set activity and the placeholder shows a small monochrome orb with its label above the shapes, so the user sees what the agent is doing, not just that it's busy. Each activity has its own glyph:

Thinking…
pulse
Searching…
orbit
Writing…
sweep
Planning…
globe
Waiting…
spark
activityOrb variantDefault labelMotion
thinkingpulseThinking…A wave spreading from the centre of a 3×3 grid
searchingorbitSearching…A comet chasing round a ring of dots
writingsweepWriting…A band moving left to right across the grid
planningglobePlanning…Dots circling a flattened globe
waitingsparkWaiting…A breathing ✦ with a soft echo

label replaces the default text ("Searching the ledger…"). The orb ships as its own file, components/genui/orb.tsx, and you can use it on its own: <Orb variant="orbit" pill />. It takes variant, size (default 20), label, pill and announce (set it to false inside another status region). It's drawn with currentColor and animated with motion, so it needs no extra CSS. Adapted from Halaska UI's Orb (MIT).

Accessibility

  • The root is role="status" with aria-busy="true", and an sr-only label ("Generating…" by default) is announced politely. The skeleton shapes themselves are hidden from assistive tech.
  • With activity set, the label is shown next to the orb instead of sr-only, and the root announces it. The orb glyph is hidden from assistive tech.
  • With reduced motion turned on, the pulse stops, the shapes stay still and the orb's dots stop moving.

Installation

npx shadcn@latest add https://your-site.com/r/streaming-skeleton.json

Usage

There's no tool to register. Render the placeholder for a tool part that has no name or no input yet:

components/message.tsx
import { GenUIPlaceholder } from "@/components/genui/streaming-skeleton"

const shapes = {
  show_table: "table",
  show_checklist: "list",
  answer_with_sources: "text",
} as const

function ToolPart({ part }) {
  if (part.state === "input-streaming" && part.input === undefined) {
    return <GenUIPlaceholder shape={shapes[part.toolName] ?? "card"} />
  }
  return <GenUIRenderer call={fromAISDK(part)} catalog={catalog} />
}

With LangGraph

Render it for a UI message whose name or props haven't arrived yet, then hand the message to the Renderer once they have.

Schema

PropTypeNotes
shapecard | table | list | text | mediaDefault card
linesnumber?1–12 rows or lines. Each shape has its own default
labelstring?Status text: sr-only, or shown next to the orb when activity is set. Default "Generating…", or the activity's word
activitythinking | searching | writing | planning | waitingOptional. Shows the activity orb

Host props: className. It is always in the streaming state (data-status="streaming").

Source

On this page