Elements

83 / 122 · Composer

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

Installation

npx shadcn@latest add "@assistant-ui/elements-context-breakdown"

Usage

import { ContextBreakdown } from "@/components/elements/context-breakdown";

<ContextBreakdown segments={segments} limit={128000} />

Props

ContextBreakdown

segments*ContextSegment[]What is occupying the window, in stacking order. Headroom is derived, not passed.
limit*numberWindow size. The header turns amber once the segments pass 85 percent of it. A zero limit is treated as zero pressure rather than dividing by it.
classNamestringExtra classes merged onto the root.

ContextSegment

label*stringWhat this slice of the window is, shown in the legend.
tokens*numberRaw token count. The element formats it.
tint*stringBackground class shared by the bar slice and the legend dot, so the two always agree.

Source

context-breakdown.tsx
"use client";

import { cn } from "@/lib/utils";
import { mono, paper } from "./surfaces";

const fmt = (n: number) => n.toLocaleString("en-US");

export interface ContextSegment {
  label: string;
  tokens: number;
  tint: string;
}

export function ContextBreakdown({
  segments,
  limit,
  className,
}: {
  segments: readonly ContextSegment[];
  limit: number;
  className?: string;
}) {
  const used = segments.reduce((sum, segment) => sum + segment.tokens, 0);
  const pressure = limit === 0 ? 0 : used / limit;
  const share = (tokens: number) => (limit === 0 ? 0 : (tokens / limit) * 100);

  return (
    <div
      className={cn(
        paper,
        "flex w-full max-w-sm flex-col gap-3 rounded-2xl p-4",
        className,
      )}
    >
      <div className="flex items-baseline justify-between">
        <span className="text-[13.5px] font-medium">Context</span>
        <span
          className={cn(
            mono,
            "tabular-nums",
            pressure > 0.85
              ? "text-amber-600 dark:text-amber-400"
              : "text-foreground/35",
          )}
        >
          {fmt(used)} / {fmt(limit)}
        </span>
      </div>

      <div className="bg-foreground/[0.06] flex h-2 w-full overflow-hidden rounded-full">
        {segments.map((segment) => (
          <span
            key={segment.label}
            className={cn(
              "h-full transition-[width] duration-500 ease-out motion-reduce:transition-none",
              segment.tint,
            )}
            style={{ width: `${share(segment.tokens)}%` }}
          />
        ))}
      </div>

      <div className="flex flex-col gap-1.5">
        {segments.map((segment) => (
          <div key={segment.label} className="flex items-center gap-2">
            <span
              aria-hidden
              className={cn("size-2 shrink-0 rounded-full", segment.tint)}
            />
            <span className="text-foreground/70 min-w-0 flex-1 truncate text-[13px]">
              {segment.label}
            </span>
            <span
              className={cn(mono, "text-foreground/35 shrink-0 tabular-nums")}
            >
              {fmt(segment.tokens)}
            </span>
          </div>
        ))}
        <div className="flex items-center gap-2">
          <span
            aria-hidden
            className="bg-foreground/[0.08] size-2 shrink-0 rounded-full"
          />
          <span className="text-foreground/35 min-w-0 flex-1 truncate text-[13px]">
            Headroom
          </span>
          <span
            className={cn(mono, "text-foreground/25 shrink-0 tabular-nums")}
          >
            {fmt(Math.max(0, limit - used))}
          </span>
        </div>
      </div>
    </div>
  );
}