# Context Meter

> Token / context-window usage as a compact ring or bar with warning and danger thresholds, rolling numbers and a segmented breakdown card.

- **Slug:** `context-meter` · **Category:** agent · **Status:** beta
- **Import:** `import { ContextMeter, ContextSegment } from "@kura-ui/react/context-meter";`
- **Docs:** https://kuro.abstergo.fyi/components/context-meter
- **Registry item:** https://kuro.abstergo.fyi/r/context-meter.json
- **Tags:** tokens, context-window, usage, meter, budget, progress, llm, limits

## Install

```bash
# shadcn registry (copies source into your project)
npx shadcn@latest add https://kuro.abstergo.fyi/r/context-meter.json
# Kura CLI (same, plus dependency resolution and --json output)
npx @kura-ui/cli add context-meter
# npm package
npm install @kura-ui/react
```

## When to use

- Show how much of a model's context window a conversation uses (e.g. in a composer footer or chat header).
- Break usage down by source — system prompt, tool definitions, attached files, history — in a hover card.
- Warn before the context fills up (auto-compaction, truncation) with warning/danger thresholds.
- Show any other token budget (per-request output limit, monthly quota) as a bar.

## When not to use

- Indeterminate progress or “thinking” — use shimmer-text or reasoning.
- Upload or task progress — use a progress bar (or agent-timeline for multi-step work).
- Billing/cost dashboards with history — use a chart.

## Props

| Prop | Type | Default | Required | Description |
| --- | --- | --- | --- | --- |
| `max` | `number` | — | yes | Context window size in tokens, e.g. 200000. |
| `used` | `number` | — | no | Tokens used. Default: the sum of `segments`. |
| `segments` | `readonly ContextSegment[]` | — | no | Breakdown: { id, label, tokens, color? }[], e.g. system / tools / files / history. Enables the hover card and the segmented bar. Colors default to ink tints. |
| `variant` | `"ring" \| "bar"` | `"ring"` | no | ring = 18px donut + value for tight toolbars; bar = horizontal (segmented) bar + value. |
| `warningAt` | `number` | `0.7` | no | Fraction (0–1) from which the meter is amber. |
| `dangerAt` | `number` | `0.9` | no | Fraction (0–1) from which the meter is red. |
| `valueDisplay` | `"percent" \| "tokens" \| "none"` | — | no | Value next to the meter: 42% or 84.2k / 200k. Default percent for ring, tokens for bar. |
| `label` | `string` | `"Context"` | no | Name used in the accessible label and the card title (“Context window”). |
| `footer` | `React.ReactNode` | — | no | Note at the bottom of the card. Defaults to a warning sentence at warning/danger levels. |
| `open` | `boolean` | — | no | Hover card open (controlled). |
| `onOpenChange` | `(open: boolean) => void` | — | no | Called when the hover card opens or closes. |

Native attributes of the root element, `className` and `ref` are passed through.

## Anatomy

- **ContextMeter** (`data-slot="context-meter"`) — Trigger button. `data-variant`, `data-level="ok" | "warning" | "danger"`.
- **Ring** (`data-slot="context-meter-ring"`) — SVG donut (aria-hidden).
- **Bar** (`data-slot="context-meter-bar"`) — Track + fill; segmented by `segments` while level is ok, a single amber/red fill above the thresholds.
- **Value** (`data-slot="context-meter-value"`) — Mono tabular value that rolls to new numbers.
- **Card** (`data-slot="context-meter-card"`) — Hover/click card: totals, segmented bar, legend with tokens and % of the window, free space, footer.
- **Segment** (`data-slot="context-meter-segment"`) — One legend row.

## Accessibility

- The meter is a button whose accessible name carries the full reading: “Context: 84,210 of 200,000 tokens used (42%, filling up)”.
- The breakdown opens on hover (300ms delay) and on click/Enter, without moving focus; Escape closes it.
- Levels are conveyed in text (name + card footer), not only by colour.
- Reduced motion: Numbers, ring and bar jump to the new value instead of rolling/growing; the card only fades.

| Keys | Action |
| --- | --- |
| Enter / Space | Open or close the breakdown card |
| Escape | Close the card |

## Notes for agents

- Pass `segments` whenever you know the breakdown; the meter then sums them and gets a hover card. Without segments it is a plain reading (no card).
- Use the provider's real window size for `max` (e.g. 200000) and recount after every turn — counts roll smoothly between values.
- For a quota rather than a context window, set `label` (e.g. "Output") and use variant="bar".

## Examples

### Ring (minimal)

Used and max; nothing else needed.

```tsx
import { ContextMeter } from "@kura-ui/react/context-meter";

export function ContextMeterMinimal() {
  return <ContextMeter used={84_210} max={200_000} />;
}
```

### Thread header with a breakdown (full)

A ring in the thread header and a bar with a footer note, both with the segment breakdown on hover. Attach logs to watch the numbers roll into warning and danger.

```tsx
"use client";

import { Paperclip } from "lucide-react";
import * as React from "react";
import { Button } from "@kura-ui/react/button";
import { ContextMeter, type ContextSegment } from "@kura-ui/react/context-meter";

const BASE: ContextSegment[] = [
  { id: "system", label: "System prompt", tokens: 3_400 },
  { id: "tools", label: "Tool definitions", tokens: 11_800 },
  { id: "files", label: "Attached files", tokens: 38_600 },
  { id: "history", label: "Conversation", tokens: 30_410 },
];

export function ContextMeterFull() {
  const [logs, setLogs] = React.useState(0);
  const segments = BASE.map((s) =>
    s.id === "files" ? { ...s, tokens: s.tokens + logs * 24_000 } : s,
  );
  return (
    <div className="flex w-full max-w-md flex-col rounded-kura-xl bg-kura-surface shadow-kura-sm">
      <div className="flex items-center justify-between gap-3 px-4 py-3">
        <div className="flex min-w-0 flex-col gap-0.5">
          <span className="truncate font-medium text-kura-fg text-sm tracking-[-0.01em]">
            Auth middleware refactor
          </span>
          <span className="text-kura-fg-subtle text-xs">32 turns · 200k context window</span>
        </div>
        <ContextMeter
          max={200_000}
          segments={segments}
          variant="ring"
          valueDisplay="percent"
          warningAt={0.7}
          dangerAt={0.9}
          label="Context"
        />
      </div>
      <div className="flex items-center justify-between gap-3 px-4 py-3 shadow-[inset_0_1px_0_var(--kura-border-subtle)]">
        <ContextMeter
          max={200_000}
          segments={segments}
          variant="bar"
          valueDisplay="tokens"
          className="-ml-1.5"
          footer="Files count once per turn; tool definitions are cached."
        />
        <div className="flex items-center gap-1">
          {logs > 0 && (
            <Button variant="ghost" size="xs" onClick={() => setLogs(0)}>
              Clear
            </Button>
          )}
          <Button variant="secondary" size="xs" onClick={() => setLogs((n) => n + 1)}>
            <Paperclip /> Attach server.log
          </Button>
        </div>
      </div>
    </div>
  );
}
```

### Thresholds (state)

ok, warning and danger bars in a list of threads, each labelled by its thread.

```tsx
import { ContextMeter } from "@kura-ui/react/context-meter";

const THREADS = [
  { title: "Onboarding emails", used: 52_000 },
  { title: "Q3 data migration", used: 98_500 },
  { title: "Incident review: checkout", used: 121_900 },
];

export function ContextMeterThresholds() {
  return (
    <ul className="flex w-full max-w-md flex-col">
      {THREADS.map((t) => (
        <li
          key={t.title}
          className="flex h-10 items-center justify-between gap-4 text-[13px] text-kura-fg shadow-[inset_0_-1px_0_var(--kura-border-subtle)] last:shadow-none"
        >
          <span className="min-w-0 truncate">{t.title}</span>
          <ContextMeter max={128_000} used={t.used} variant="bar" label={t.title} />
        </li>
      ))}
    </ul>
  );
}
```

### In a composer footer (composition)

Next to the attach and send IconButtons of a composer.

```tsx
import { ArrowUp, Paperclip } from "lucide-react";
import { IconButton } from "@kura-ui/react/icon-button";
import { ContextMeter, type ContextSegment } from "@kura-ui/react/context-meter";

const BASE: ContextSegment[] = [
  { id: "system", label: "System prompt", tokens: 3_400 },
  { id: "tools", label: "Tool definitions", tokens: 11_800 },
  { id: "files", label: "Attached files", tokens: 38_600 },
  { id: "history", label: "Conversation", tokens: 30_410 },
];

export function ContextMeterInComposer() {
  return (
    <div className="flex w-full max-w-lg flex-col gap-2 rounded-kura-xl bg-kura-surface p-3 shadow-kura-sm">
      <p className="min-h-12 px-1 text-[15px] text-kura-fg-subtle">Ask a follow-up…</p>
      <div className="flex items-center justify-between">
        <div className="flex items-center gap-1">
          <IconButton label="Attach files" size="sm" icon={<Paperclip />} />
          <ContextMeter max={200_000} segments={BASE} />
        </div>
        <IconButton
          label="Send"
          variant="primary"
          size="sm"
          icon={<ArrowUp />}
          className="rounded-full"
        />
      </div>
    </div>
  );
}
```

## Dependencies

- npm: none
- Kura registry items: `kura-theme`, `popover`, `use-controllable-state`, `use-reduced-motion`, `utils`
- Related: `composer`, `model-picker`, `message`, `tool-call`
