# 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} />;
}
```

### Ring and bar with a breakdown (full)

Segments, thresholds, value display and a footer. Add tokens to watch the numbers roll into warning and danger.

```tsx
"use client";

import { Plus } 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 [history, setHistory] = React.useState(30_410);
  const segments = BASE.map((s) => (s.id === "history" ? { ...s, tokens: history } : s));
  return (
    <div className="flex flex-col items-center gap-6">
      <div className="flex items-center gap-6">
        <ContextMeter
          max={200_000}
          segments={segments}
          variant="ring"
          valueDisplay="percent"
          warningAt={0.7}
          dangerAt={0.9}
          label="Context"
        />
        <ContextMeter
          max={200_000}
          segments={segments}
          variant="bar"
          valueDisplay="tokens"
          footer="Files count once per turn; tool definitions are cached."
        />
      </div>
      <div className="flex gap-2">
        <Button variant="secondary" size="sm" onClick={() => setHistory((h) => h + 24_000)}>
          <Plus /> Add 24k tokens
        </Button>
        <Button variant="ghost" size="sm" onClick={() => setHistory(30_410)}>
          Reset
        </Button>
      </div>
    </div>
  );
}
```

### Thresholds (state)

ok, warning and danger bars.

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

export function ContextMeterThresholds() {
  return (
    <div className="flex flex-col items-start gap-3">
      <ContextMeter max={128_000} used={52_000} variant="bar" />
      <ContextMeter max={128_000} used={98_500} variant="bar" />
      <ContextMeter max={128_000} used={121_900} variant="bar" />
    </div>
  );
}
```

### In a composer footer (composition)

Next to an IconButton send action.

```tsx
import { ArrowUp } 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">
        <ContextMeter max={200_000} segments={BASE} />
        <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`
