For the complete documentation index, see llms.txt. Markdown variants are available by appending .md to documentation URLs. Homepage markdown is available at /llms.md/content.md. An agent skill is available at /.well-known/agent-skills/site-skill.md.

Agent Limits Card 01

A compact view of context capacity and usage allowances.

Expanded

Open the context breakdown by default with defaultExpanded. Each group can be opened independently. Deferred segments appear in the breakdown but do not count toward capacity.

Installation

Set up the @cobalt registry first, as described in Installation, then add the block:

$ pnpm dlx shadcn@latest add @cobalt/agent-limits-card-01

Pass a context capacity, segments, optional groups, and usage limits. Set expanded and onExpandedChange to control the breakdown externally.

Source

"use client";

import {
  RiArrowRightLine,
  RiArrowDownSLine,
  RiArrowRightSLine,
} from "@remixicon/react";
import { cn } from "cn";
import * as React from "react";

import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { Progress } from "@/components/ui/progress";
import { Separator } from "@/components/ui/separator";

export interface AgentLimitSegment {
  label: string;
  tokens: number;
  color?: string;
  deferred?: boolean;
}
export interface AgentLimitGroup {
  label: string;
  items: { label: string; tokens: number }[];
}
export interface AgentUsageLimit {
  label: string;
  used: number;
  resets: string;
}
export interface AgentLimitsCardProps {
  context: {
    max: number;
    segments: AgentLimitSegment[];
    groups?: AgentLimitGroup[];
  };
  plan: string;
  planHref?: string;
  limits: AgentUsageLimit[];
  defaultExpanded?: boolean;
  expanded?: boolean;
  onExpandedChange?: (open: boolean) => void;
  className?: string;
}

const palette = [
  "var(--ring)",
  "var(--chart-5)",
  "var(--chart-3)",
  "var(--chart-1)",
  "var(--status-dot-yellow-dot, var(--foreground))",
  "var(--destructive)",
];
const number = new Intl.NumberFormat("en", {
  notation: "compact",
  maximumFractionDigits: 1,
});
const clamp = (value: number) => Math.min(1, Math.max(0, value));

function Count({
  value,
  compact = false,
}: {
  value: number;
  compact?: boolean;
}) {
  const [display, setDisplay] = React.useState(value);
  const previous = React.useRef(value);
  React.useEffect(() => {
    const media = window.matchMedia("(prefers-reduced-motion: reduce)");
    const from = previous.current;
    if (media.matches || from === value) {
      previous.current = value;
      setDisplay(value);
      return;
    }
    const start = performance.now();
    let frame = 0;
    const tick = (now: number) => {
      const t = Math.min(1, (now - start) / 320);
      const next = Math.round(from + (value - from) * (1 - (1 - t) ** 3));
      previous.current = next;
      setDisplay(next);
      if (t < 1) {
        frame = requestAnimationFrame(tick);
      }
    };
    frame = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(frame);
  }, [value]);
  return <>{compact ? number.format(display) : display}</>;
}

export function AgentLimitsCard({
  context,
  plan,
  planHref,
  limits,
  defaultExpanded,
  expanded,
  onExpandedChange,
  className,
}: AgentLimitsCardProps) {
  const max = Math.max(0, context.max);
  const used = context.segments.reduce(
    (sum, segment) =>
      sum + (segment.deferred ? 0 : Math.max(0, segment.tokens)),
    0
  );
  const ratio = max > 0 ? used / max : 0;
  const remaining = Math.max(0, max - used);
  return (
    <Card
      role="region"
      aria-label="Agent usage"
      variant="muted"
      className={cn("@container w-full max-w-[400px]", className)}
    >
      <CardContent className="gap-0">
        <div className="text-sm leading-4.5 text-foreground">
          <Collapsible
            defaultOpen={defaultExpanded}
            open={expanded}
            onOpenChange={onExpandedChange}
          >
            <CollapsibleTrigger
              render={<Button variant="unstyled" radius={4} />}
              className="group block w-full text-left whitespace-normal"
            >
              <span className="flex w-full flex-wrap items-center gap-2 font-normal">
                <span className="flex-1 font-medium text-muted-foreground">
                  Context capacity
                </span>
                <span className="text-muted-foreground tabular-nums">
                  <Count value={used} compact /> / <Count value={max} compact />{" "}
                  <span className="font-medium text-foreground">
                    (<Count value={Math.round(ratio * 100)} />
                    %)
                  </span>
                </span>
                <RiArrowDownSLine
                  aria-hidden
                  className="size-3 text-muted-foreground transition-transform duration-200 group-data-panel-open:rotate-180 motion-reduce:transition-none"
                />
              </span>
            </CollapsibleTrigger>
            <div
              role="img"
              aria-label={`${number.format(used)} of ${number.format(max)} tokens used`}
              className="mt-2 flex h-1.5 origin-left overflow-hidden rounded-full bg-border agent-limits-card-01-draw"
            >
              {context.segments.map(
                (segment, index) =>
                  !segment.deferred && (
                    <span
                      key={index}
                      className="w-(--segment-width) bg-(--segment-color)"
                      style={
                        {
                          "--segment-width": `${max > 0 ? clamp(Math.max(0, segment.tokens) / max) * 100 : 0}%`,
                          "--segment-color":
                            segment.color ?? palette[index % palette.length],
                        } as React.CSSProperties
                      }
                    />
                  )
              )}
            </div>
            <CollapsibleContent>
              <div className="flex flex-col gap-2 pt-4">
                {context.segments.map((segment, index) => (
                  <div
                    key={index}
                    style={
                      { "--delay": `${index * 40}ms` } as React.CSSProperties
                    }
                    className="agent-limits-card-01-enter grid grid-cols-[minmax(0,1fr)_auto_48px] items-center gap-2 tabular-nums"
                  >
                    <span className="flex min-w-0 items-center gap-2">
                      <span
                        aria-hidden
                        className="size-2 shrink-0 rounded-[2px] bg-(--segment-color)"
                        style={
                          {
                            "--segment-color": segment.deferred
                              ? "var(--border)"
                              : (segment.color ??
                                palette[index % palette.length]),
                          } as React.CSSProperties
                        }
                      />
                      <span className="min-w-0 break-words">
                        {segment.label}
                      </span>
                    </span>
                    <span className="text-muted-foreground">
                      {number.format(segment.tokens)}
                    </span>
                    <span className="text-right font-medium">
                      {segment.deferred
                        ? "—"
                        : `${(max > 0 ? (segment.tokens / max) * 100 : 0).toFixed(1)}%`}
                    </span>
                  </div>
                ))}
                <div className="grid grid-cols-[minmax(0,1fr)_auto_48px] items-center gap-2 tabular-nums">
                  <span className="flex items-center gap-2">
                    <span
                      aria-hidden
                      className="size-2 rounded-[2px] bg-border"
                    />
                    Available
                  </span>
                  <span className="text-muted-foreground">
                    {number.format(remaining)}
                  </span>
                  <span className="text-right font-medium">
                    {(max > 0 ? (remaining / max) * 100 : 0).toFixed(1)}%
                  </span>
                </div>
                {context.groups?.map((group, index) => (
                  <Collapsible key={index} className="group/group">
                    <CollapsibleTrigger
                      render={<Button variant="unstyled" radius={4} />}
                      className="block w-full text-left whitespace-normal"
                    >
                      <span className="flex w-full items-center gap-2 py-1 font-normal text-muted-foreground">
                        <RiArrowRightSLine
                          aria-hidden
                          className="size-3 shrink-0 transition-transform duration-200 group-data-panel-open/group:rotate-90 motion-reduce:transition-none"
                        />
                        <span className="min-w-0 flex-1 break-words">
                          {group.label}
                        </span>
                        <span className="tabular-nums">
                          {number.format(
                            group.items.reduce(
                              (sum, item) => sum + item.tokens,
                              0
                            )
                          )}
                        </span>
                        <span className="w-12 text-right tabular-nums">
                          {group.items.length}
                        </span>
                      </span>
                    </CollapsibleTrigger>
                    <CollapsibleContent>
                      <div className="flex flex-col gap-2 py-1 pl-5">
                        {group.items.map((item, itemIndex) => (
                          <div
                            key={itemIndex}
                            className="flex justify-between gap-2 text-muted-foreground tabular-nums"
                          >
                            <span className="min-w-0 break-words">
                              {item.label}
                            </span>
                            <span>{number.format(item.tokens)}</span>
                          </div>
                        ))}
                      </div>
                    </CollapsibleContent>
                  </Collapsible>
                ))}
              </div>
            </CollapsibleContent>
          </Collapsible>
          <Separator className="my-4" />
          <div className="flex flex-col gap-3">
            {planHref ? (
              <a
                href={planHref}
                className="flex items-center justify-between gap-2 rounded-[4px] font-medium text-muted-foreground transition-colors duration-150 hover:text-foreground motion-reduce:transition-none focus-visible:outline-2 focus-visible:outline-ring"
              >
                Usage allowance · {plan}
                <RiArrowRightLine aria-hidden className="size-3" />
              </a>
            ) : (
              <div className="font-medium text-muted-foreground">
                Usage allowance · {plan}
              </div>
            )}
            {limits.map((limit, index) => (
              <div
                key={index}
                style={{ "--delay": `${index * 40}ms` } as React.CSSProperties}
                className="agent-limits-card-01-enter flex flex-col gap-1.5"
              >
                <div className="flex items-baseline gap-2">
                  <span className="min-w-0 flex-1 font-medium">
                    {limit.label}
                  </span>
                  <span className="hidden text-right text-muted-foreground @[300px]:block">
                    {limit.resets}
                  </span>
                  <span className="font-medium tabular-nums">
                    <Count value={Math.round(clamp(limit.used) * 100)} />%
                  </span>
                </div>
                <Progress
                  aria-label={limit.label}
                  value={Math.round(clamp(limit.used) * 100)}
                />
              </div>
            ))}
          </div>
        </div>
      </CardContent>
    </Card>
  );
}