Elements

Context breakdown

Where the window actually went: prompt, tools, files, conversation, and what's left.

Context50,805 / 128,000
System prompt810
Tools1,890
Attached files17,325
Conversation30,780
Headroom77,195
fig. 01 · plays once, replay from the corner

Installation

npx shadcn@latest add "@assistant-ui/elements-context-breakdown"
First time? Set up a runtime

Runtime components read their state from an assistant-ui runtime. Add one to an existing project:

npx assistant-ui@latest init

Then wrap your app in a runtime provider:

import { AssistantRuntimeProvider } from "@assistant-ui/react";
import { useChatRuntime, AssistantChatTransport } from "@assistant-ui/ai-sdk";

export default function App() {
  const runtime = useChatRuntime({
    transport: new AssistantChatTransport({ api: "/api/chat" }),
  });

  return (
    <AssistantRuntimeProvider runtime={runtime}>
      {/* your components */}
    </AssistantRuntimeProvider>
  );
}

The installation guide covers new projects, templates, and API routes.

A labeled bar and legend showing what is occupying the model's context window, with the leftover headroom computed rather than passed in. With a runtime two of those labels come from the thread's real per-turn token usage; standalone every segment, and its color, is yours to define.

Getting started

With a runtime, assistant-ui does not label context usage by category the way this element does. What a ChatModelAdapter can report is two real numbers per turn: input tokens and output tokens, carried on each assistant message's generation steps.

Derive the segments that are actually real

A finer split, separating tool schema tokens from the rest of the prompt, or metering files apart from the running conversation, is not something the thread state tracks: providers report input as one combined figure. Stick to the two segments the runtime can tell you about, and add flat, application-known segments (a fixed system-prompt cost, for instance) alongside them only when you already track that cost yourself.

components/assistant-ui/elements/thread-context-breakdown.tsx
"use client";

import { useAuiState } from "@assistant-ui/react";
import {
  ContextBreakdown,
  type ContextSegment,
} from "@/components/assistant-ui/elements/context-breakdown";

function useTurnUsage() {
  return useAuiState((s) =>
    s.thread.messages.reduce(
      (sum, message) => {
        for (const step of message.metadata.steps ?? []) {
          sum.input += step.usage?.inputTokens ?? 0;
          sum.output += step.usage?.outputTokens ?? 0;
        }
        return sum;
      },
      { input: 0, output: 0 },
    ),
  );
}

const MODEL_CONTEXT_WINDOW = 128_000;

export function ThreadContextBreakdown() {
  const usage = useTurnUsage();
  const segments: ContextSegment[] = [
    { label: "Prompt", tokens: usage.input, tint: "bg-foreground/30" },
    { label: "Reply", tokens: usage.output, tint: "bg-blue-500/70" },
  ];
  return <ContextBreakdown segments={segments} limit={MODEL_CONTEXT_WINDOW} />;
}

Anatomy

<div data-slot="context-breakdown">
  <div>
    <span>Context</span>
    <span>{/* used / limit, amber past 85% */}</span>
  </div>
  <div>{/* stacked bar, one named meter per segment, widths from tokens / limit */}</div>
  <div>
    {/* one row per segment: dot, label, count */}
    <div>{/* Headroom row: fixed dot, limit minus used, clamped at 0 */}</div>
  </div>
</div>

Headroom is not a segment you pass; it is limit minus the sum of every segment's tokens, floored at zero, and it never gets a bar slice of its own, only a legend row. A zero limit is treated as zero pressure rather than dividing by it, so the header stays plain instead of showing a red or broken percentage. Every segment supplies its own tint, a Tailwind background class shared verbatim between the bar slice and the legend dot, so the two never disagree. Each painted segment is a named meter whose 0…100 value matches its share of the context limit and whose value text reads the same token count the legend prints. A segment that rounds to no announced width is left out of the bar entirely rather than announced as an empty one.

Examples

Choosing tints

Any Tailwind background utility works; segments read cleanly when their tints step through opacity or hue together, as in the runtime example above.

{ label: "Conversation", tokens: 42000, tint: "bg-blue-500/70" }

Restyle the panel

<ContextBreakdown className="max-w-xs gap-2" segments={segments} limit={limit} />

API reference

Thread state

SelectorTypeDescription
s.thread.messagesreadonly MessageState[]Every message in the active thread. Each assistant message's metadata.steps is a readonly ThreadStep[], and a step may carry usage: { inputTokens: number; outputTokens: number } when the model adapter reports it.

There is no selector for the model's context window size or for a category split beyond input and output; both are supplied as shown above.