48 / Agent UI
Token Meter
How much of the context window is gone, split by what spent it, and a warning before it runs out.
- System4.2k
- History71k
- Files18k
"use client"
import { DemoVariants } from "@/components/demos/demo-variants"
import { TokenMeter } from "mischief-ui/token-meter"
export function TokenMeterDemo() {
return (
<DemoVariants
label="Usage"
variants={[
{
id: "segments",
label: "By part",
render: () => (
<div className="w-full max-w-sm">
<TokenMeter
limit={200_000}
segments={[
{ label: "System", value: 4_200 },
{ label: "History", value: 71_000 },
{ label: "Files", value: 18_400 },
]}
/>
</div>
),
},
{
id: "plain",
label: "Total only",
render: () => (
<div className="w-full max-w-sm">
<TokenMeter used={38_000} limit={200_000} />
</div>
),
},
{
id: "tight",
label: "Running out",
render: () => (
<div className="w-full max-w-sm">
<TokenMeter used={186_500} limit={200_000} />
</div>
),
},
]}
/>
)
}Installation
Copy the source into your project, or keep it behind a package.
npx shadcn@latest add Tinkerers-Labs/mischief-ui/token-meterimport { TokenMeter } from "mischief-ui/token-meter"Or paste it in yourself. The source imports the shared cn helper from @/lib/utils, so point that at your own copy.
"use client" import * as React from "react" import { cn } from "@/lib/utils" export type TokenSegment = { label: string value: number /** Any CSS colour. Defaults to a shade drawn from the theme. */ color?: string} export type TokenMeterProps = Omit<Usage
export function Usage() {
return (
<TokenMeter
limit={200_000}
segments={[
{ label: "System", value: 4_200 },
{ label: "History", value: 96_000 },
]}
/>
)
}Showing where it went
A single number tells someone they are running out; segments tell them what to do about it. Splitting the bar by what spent the budget -- the system prompt, the history, attached files -- turns a warning into a decision, because the largest band is the thing worth dropping.
<TokenMeter
limit={200_000}
segments={[
{ label: "System", value: 4_200 },
{ label: "History", value: 71_000 },
{ label: "Files", value: 18_400 },
]}
/>Order them by how permanent they are, most fixed first, so the part someone can actually reduce ends up at the changing edge of the bar rather than in the middle.
Running out
Past warnAt -- four fifths of the limit by default -- the reading turns and the component marks itself tight, which you can style against. Lower it when hitting the limit is expensive to recover from, so the warning arrives while there is still room to act on it.
format decides how both numbers read. The default abbreviates, which is right for a window of two hundred thousand; pass your own where exactness matters, or where the budget is money rather than tokens.
API
limitnumberThe window. Values at or past it read as full.usednumberTotal consumed. Ignored when segments are given.segmentsTokenSegment[]Named parts that sum to the total, each with its own colour.warnAtnumberFraction of the limit that reads as tight. Defaults to 0.8.format(value: number) => stringFormats both numbers. Defaults to a compact form.labelstringNames the meter. Defaults to "Context used".showLegendbooleanShows the segment key. Defaults to true.TokenSegment
labelstringNamed in the key beneath the bar.valuenumberCounted towards the total.colorstringAny CSS colour. A theme shade is used when omitted.Accessibility
The bar carries meter semantics with its real minimum, maximum, and current value, plus text saying the same thing in words and a percentage, so it is never read by colour alone. Segment colours are repeated in a written key. The bar animates its width and stops doing so under reduced motion.