AI
Token meter
Shows how much of a context window or usage budget is spent, split by what spent it.
- Built on
role="meter"- Client JavaScript
- None
- Works without React
- Yes, as HTML and CSS
- Source, gzipped
- 2.4 kB
- Dependencies
- React
- System12.4k
- Messages96.3k
- Files33.3k
- Free58k
Plenty of room
Try its options
Loading its options.
Code
import { TokenMeter } from "@/components/equip/token-meter";
export default function Demo() {
return (
<TokenMeter
style={{ width: 224 }}
label="Context window"
limit={200_000}
segments={[
{ label: "System", value: 12_400 },
{ label: "Messages", value: 96_300 },
{ label: "Files", value: 33_300 },
]}
/>
);
}Your versionchange its options in Look, This part
HTMLworks in any framework, with the part's CSS
<div style="width:224px" class="eq-token-meter" data-state="ok">
<div class="eq-token-meter-head" aria-hidden="true">
<span class="eq-token-meter-label">Context window</span>
<span class="eq-token-meter-figure">142k of 200k</span>
</div>
<div class="eq-token-meter-bar" role="meter" aria-label="Context window" aria-valuemin="0" aria-valuemax="200000" aria-valuenow="142000" aria-valuetext="142k of 200k tokens used. Plenty of room.">
<span class="eq-token-meter-segment" style="--_share:6.2%"></span>
<span class="eq-token-meter-segment" style="--_share:48.15%"></span>
<span class="eq-token-meter-segment" style="--_share:16.650000000000002%"></span>
</div>
<ul class="eq-token-meter-legend">
<li class="eq-token-meter-item">
<span class="eq-token-meter-swatch" aria-hidden="true"></span>
<span class="eq-token-meter-name">System</span>
<span class="eq-token-meter-amount">12.4k</span>
</li>
<li class="eq-token-meter-item">
<span class="eq-token-meter-swatch" aria-hidden="true"></span>
<span class="eq-token-meter-name">Messages</span>
<span class="eq-token-meter-amount">96.3k</span>
</li>
<li class="eq-token-meter-item">
<span class="eq-token-meter-swatch" aria-hidden="true"></span>
<span class="eq-token-meter-name">Files</span>
<span class="eq-token-meter-amount">33.3k</span>
</li>
<li class="eq-token-meter-item" data-free="">
<span class="eq-token-meter-swatch" aria-hidden="true"></span>
<span class="eq-token-meter-name">Free</span>
<span class="eq-token-meter-amount">58k</span>
</li>
</ul>
<p class="eq-token-meter-state">Plenty of room</p>
</div>Examples
Getting full and over the limit
The state is written out in words, so it never depends on the colour of the dot.
- System8.2k
- Messages71.5k
- Files29.8k
- Free18.5k
Getting full
- System8.2k
- Messages94.1k
- Files41.7k
Over the limit
Code
import { TokenMeter } from "@/components/equip/token-meter";
export default function Example() {
return (
<div style={{ display: "flex", flexDirection: "column", gap: 24, maxWidth: 360 }}>
<TokenMeter
label="Context window"
limit={128_000}
segments={[
{ label: "System", value: 8_200 },
{ label: "Messages", value: 71_500 },
{ label: "Files", value: 29_800 },
]}
/>
<TokenMeter
label="Context window"
limit={128_000}
segments={[
{ label: "System", value: 8_200 },
{ label: "Messages", value: 94_100 },
{ label: "Files", value: 41_700 },
]}
/>
</div>
);
}HTMLworks in any framework, with the part's CSS
<div style="display:flex;flex-direction:column;gap:24px;max-width:360px">
<div class="eq-token-meter" data-state="warn">
<div class="eq-token-meter-head" aria-hidden="true">
<span class="eq-token-meter-label">Context window</span>
<span class="eq-token-meter-figure">109.5k of 128k</span>
</div>
<div class="eq-token-meter-bar" role="meter" aria-label="Context window" aria-valuemin="0" aria-valuemax="128000" aria-valuenow="109500" aria-valuetext="109.5k of 128k tokens used. Getting full.">
<span class="eq-token-meter-segment" style="--_share:6.406249999999999%"></span>
<span class="eq-token-meter-segment" style="--_share:55.859375%"></span>
<span class="eq-token-meter-segment" style="--_share:23.28125%"></span>
</div>
<ul class="eq-token-meter-legend">
<li class="eq-token-meter-item">
<span class="eq-token-meter-swatch" aria-hidden="true"></span>
<span class="eq-token-meter-name">System</span>
<span class="eq-token-meter-amount">8.2k</span>
</li>
<li class="eq-token-meter-item">
<span class="eq-token-meter-swatch" aria-hidden="true"></span>
<span class="eq-token-meter-name">Messages</span>
<span class="eq-token-meter-amount">71.5k</span>
</li>
<li class="eq-token-meter-item">
<span class="eq-token-meter-swatch" aria-hidden="true"></span>
<span class="eq-token-meter-name">Files</span>
<span class="eq-token-meter-amount">29.8k</span>
</li>
<li class="eq-token-meter-item" data-free="">
<span class="eq-token-meter-swatch" aria-hidden="true"></span>
<span class="eq-token-meter-name">Free</span>
<span class="eq-token-meter-amount">18.5k</span>
</li>
</ul>
<p class="eq-token-meter-state">Getting full</p>
</div>
<div class="eq-token-meter" data-state="over">
<div class="eq-token-meter-head" aria-hidden="true">
<span class="eq-token-meter-label">Context window</span>
<span class="eq-token-meter-figure">144k of 128k</span>
</div>
<div class="eq-token-meter-bar" role="meter" aria-label="Context window" aria-valuemin="0" aria-valuemax="128000" aria-valuenow="128000" aria-valuetext="144k of 128k tokens used. Over the limit.">
<span class="eq-token-meter-segment" style="--_share:5.694444444444445%"></span>
<span class="eq-token-meter-segment" style="--_share:65.34722222222223%"></span>
<span class="eq-token-meter-segment" style="--_share:28.958333333333336%"></span>
</div>
<ul class="eq-token-meter-legend">
<li class="eq-token-meter-item">
<span class="eq-token-meter-swatch" aria-hidden="true"></span>
<span class="eq-token-meter-name">System</span>
<span class="eq-token-meter-amount">8.2k</span>
</li>
<li class="eq-token-meter-item">
<span class="eq-token-meter-swatch" aria-hidden="true"></span>
<span class="eq-token-meter-name">Messages</span>
<span class="eq-token-meter-amount">94.1k</span>
</li>
<li class="eq-token-meter-item">
<span class="eq-token-meter-swatch" aria-hidden="true"></span>
<span class="eq-token-meter-name">Files</span>
<span class="eq-token-meter-amount">41.7k</span>
</li>
</ul>
<p class="eq-token-meter-state">Over the limit</p>
</div>
</div>Compact, in a header
Compact drops the legend and the state line; the full reading is still in the meter's value text.
Rollback plan for build 412
Code
import { TokenMeter } from "@/components/equip/token-meter";
export default function Example() {
return (
<div
style={{
display: "flex",
flexWrap: "wrap",
alignItems: "center",
justifyContent: "space-between",
gap: 16,
maxWidth: 420,
paddingBlock: 8,
borderBottom: "1px solid var(--eq-line)",
}}
>
<strong style={{ fontSize: "var(--eq-text-s)" }}>Rollback plan for build 412</strong>
<TokenMeter
compact
style={{ flex: "none", width: 140 }}
label="Context window"
limit={200_000}
segments={[
{ label: "System", value: 12_400 },
{ label: "Messages", value: 151_000 },
]}
/>
</div>
);
}HTMLworks in any framework, with the part's CSS
<div style="display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:16px;max-width:420px;padding-block:8px;border-bottom:1px solid var(--eq-line)">
<strong style="font-size:var(--eq-text-s)">Rollback plan for build 412</strong>
<div style="flex:none;width:140px" class="eq-token-meter" data-state="warn" data-compact="true">
<div class="eq-token-meter-bar" role="meter" aria-label="Context window" aria-valuemin="0" aria-valuemax="200000" aria-valuenow="163400" aria-valuetext="163.4k of 200k tokens used. Getting full.">
<span class="eq-token-meter-segment" style="--_share:6.2%"></span>
<span class="eq-token-meter-segment" style="--_share:75.5%"></span>
</div>
<span class="eq-token-meter-figure" aria-hidden="true">163.4k of 200k</span>
</div>
</div>A monthly budget in percent
- Support bot37%
- Code review28%
- Search12%
- Free22%
Getting full
Code
import { TokenMeter } from "@/components/equip/token-meter";
export default function Example() {
return (
<TokenMeter
style={{ maxWidth: 360 }}
label="Team usage this month"
format="percent"
warnAt={0.75}
limit={5_000_000}
segments={[
{ label: "Support bot", value: 1_850_000 },
{ label: "Code review", value: 1_420_000 },
{ label: "Search", value: 610_000 },
]}
/>
);
}HTMLworks in any framework, with the part's CSS
<div style="max-width:360px" class="eq-token-meter" data-state="warn">
<div class="eq-token-meter-head" aria-hidden="true">
<span class="eq-token-meter-label">Team usage this month</span>
<span class="eq-token-meter-figure">78%</span>
</div>
<div class="eq-token-meter-bar" role="meter" aria-label="Team usage this month" aria-valuemin="0" aria-valuemax="5000000" aria-valuenow="3880000" aria-valuetext="78% used. Getting full.">
<span class="eq-token-meter-segment" style="--_share:37%"></span>
<span class="eq-token-meter-segment" style="--_share:28.4%"></span>
<span class="eq-token-meter-segment" style="--_share:12.2%"></span>
</div>
<ul class="eq-token-meter-legend">
<li class="eq-token-meter-item">
<span class="eq-token-meter-swatch" aria-hidden="true"></span>
<span class="eq-token-meter-name">Support bot</span>
<span class="eq-token-meter-amount">37%</span>
</li>
<li class="eq-token-meter-item">
<span class="eq-token-meter-swatch" aria-hidden="true"></span>
<span class="eq-token-meter-name">Code review</span>
<span class="eq-token-meter-amount">28%</span>
</li>
<li class="eq-token-meter-item">
<span class="eq-token-meter-swatch" aria-hidden="true"></span>
<span class="eq-token-meter-name">Search</span>
<span class="eq-token-meter-amount">12%</span>
</li>
<li class="eq-token-meter-item" data-free="">
<span class="eq-token-meter-swatch" aria-hidden="true"></span>
<span class="eq-token-meter-name">Free</span>
<span class="eq-token-meter-amount">22%</span>
</li>
</ul>
<p class="eq-token-meter-state">Getting full</p>
</div>Props
| Prop | Type | Default | What it does |
|---|---|---|---|
label | string | none | What is measured, e.g. "Context window". Also the meter's accessible name. |
segments | { label: string; value: number }[] | none | What the spend is made of, in tokens, in the order they fill the bar. |
limit | number | none | Size of the window or budget, in tokens. |
format | "tokens" | "percent" | "tokens" | Figures as counts like "142k of 200k" or as a share like "71%". |
warnAt | number | 0.8 | Share of the limit from which the state reads Getting full. |
compact | boolean | false | A single thin bar with a number, for a header. |
...rest | ComponentProps<"div"> | none | Every native div attribute. |
Anatomy
| root | .eq-token-meter | |
|---|---|---|
| head | .eq-token-meter-head | |
| label | .eq-token-meter-label | |
| figure | .eq-token-meter-figure | Used and limit, or the share. |
| bar | .eq-token-meter-bar | The element with role meter. |
| segment | .eq-token-meter-segment | Tones step down from the ink in order. |
| legend | .eq-token-meter-legend | |
| legend item | .eq-token-meter-item | The free space is the last item, marked data-free. |
| swatch | .eq-token-meter-swatch | |
| name | .eq-token-meter-name | |
| amount | .eq-token-meter-amount | |
| state | .eq-token-meter-state | Plenty of room, Getting full or Over the limit. |
Notes
- The bar is a meter whose value text reads, for example, "142k of 200k tokens used. Plenty of room."
- Over the limit, aria-valuenow stops at the limit while the value text carries the true figure, and the bar is scaled to the total so the mix stays visible.
- Numbers are formatted by the part, not by Intl, so the server and the browser always print the same "12.4k".
- The state is written in words; the coloured dot beside it is never the only signal.
- Renders on the server with no client JavaScript.
Source
Two files, and they are the whole part. Open either to read it, copy it straight from its bar, or add the part to your loadout and install it with everything else you picked.
components/equip/token-meter.tsx113 lines
import type { ComponentProps, CSSProperties } from "react";
import "./token-meter.css";
export type TokenMeterProps = ComponentProps<"div"> & {
/** What is being measured, e.g. "Context window". Also the meter's accessible name. */
label: string;
/** What the spend is made of, in tokens, in the order they fill the bar. */
segments: { label: string; value: number }[];
/** The size of the window or budget, in tokens. */
limit: number;
/** Show figures as token counts ("142k of 200k") or as a share ("71%"). */
format?: "tokens" | "percent";
/** Share of the limit from which the state reads "Getting full". */
warnAt?: number;
/** A single thin bar with a number, for a header or toolbar. */
compact?: boolean;
};
/** "940", "12.4k", "200k", "1.2M". Hand-rolled so server and client print the same thing. */
function short(n: number): string {
const v = Math.max(0, n);
const trim = (x: number) => (Math.round(x * 10) / 10).toFixed(1).replace(/\.0$/, "");
if (v < 1000) return String(Math.round(v));
if (v < 999_950) return `${trim(v / 1000)}k`;
return `${trim(v / 1_000_000)}M`;
}
export function TokenMeter({
label,
segments,
limit,
format = "tokens",
warnAt = 0.8,
compact = false,
className,
...rest
}: TokenMeterProps) {
const used = segments.reduce((sum, s) => sum + Math.max(0, s.value), 0);
const safeLimit = Math.max(0, limit);
const share = safeLimit > 0 ? used / safeLimit : used > 0 ? Infinity : 0;
const state = share > 1 ? "over" : share >= warnAt ? "warn" : "ok";
const stateText = { ok: "Plenty of room", warn: "Getting full", over: "Over the limit" }[state];
const percent = Number.isFinite(share) ? Math.round(share * 100) : 100;
const figure = format === "percent" ? `${percent}%` : `${short(used)} of ${short(safeLimit)}`;
const spoken = format === "percent" ? `${percent}% used` : `${short(used)} of ${short(safeLimit)} tokens used`;
// Over the limit the bar is scaled to the total, so it fills and still shows the mix.
const scale = Math.max(safeLimit, used, 1);
const free = Math.max(0, safeLimit - used);
return (
<div
{...rest}
className={["eq-token-meter", className].filter(Boolean).join(" ")}
data-state={state}
data-compact={compact || undefined}
>
{!compact && (
<div className="eq-token-meter-head" aria-hidden="true">
<span className="eq-token-meter-label">{label}</span>
<span className="eq-token-meter-figure">{figure}</span>
</div>
)}
<div
className="eq-token-meter-bar"
role="meter"
aria-label={label}
aria-valuemin={0}
aria-valuemax={safeLimit}
aria-valuenow={Math.min(used, safeLimit)}
aria-valuetext={`${spoken}. ${stateText}.`}
>
{segments.map((segment, i) => (
<span
key={`${i}-${segment.label}`}
className="eq-token-meter-segment"
style={{ "--_share": `${(Math.max(0, segment.value) / scale) * 100}%` } as CSSProperties}
/>
))}
</div>
{compact ? (
<span className="eq-token-meter-figure" aria-hidden="true">
{figure}
</span>
) : (
<>
<ul className="eq-token-meter-legend">
{segments.map((segment, i) => (
<li key={`${i}-${segment.label}`} className="eq-token-meter-item">
<span className="eq-token-meter-swatch" aria-hidden="true" />
<span className="eq-token-meter-name">{segment.label}</span>
<span className="eq-token-meter-amount">
{format === "percent" && safeLimit > 0
? `${Math.round((Math.max(0, segment.value) / safeLimit) * 100)}%`
: short(segment.value)}
</span>
</li>
))}
{free > 0 && (
<li className="eq-token-meter-item" data-free="">
<span className="eq-token-meter-swatch" aria-hidden="true" />
<span className="eq-token-meter-name">Free</span>
<span className="eq-token-meter-amount">
{format === "percent" ? `${Math.round((free / safeLimit) * 100)}%` : short(free)}
</span>
</li>
)}
</ul>
<p className="eq-token-meter-state">{stateText}</p>
</>
)}
</div>
);
}components/equip/token-meter.css163 lines
@layer equip {
.eq-token-meter {
display: flex;
flex-direction: column;
gap: var(--eq-space-2);
min-width: 0;
color: var(--eq-ink);
font: var(--eq-weight) var(--eq-text-s) / 1.3 var(--eq-font);
}
.eq-token-meter[data-compact] {
flex-direction: row;
align-items: center;
}
.eq-token-meter-head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: var(--eq-space-3);
}
.eq-token-meter-label {
font-weight: var(--eq-weight-strong);
}
.eq-token-meter-figure {
color: var(--eq-ink-2);
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.eq-token-meter[data-compact] .eq-token-meter-figure {
flex: none;
font-size: var(--eq-text-xs);
}
/* Tones step down from the ink. They are translucent, so a 1px gap keeps neighbours apart. */
.eq-token-meter-segment,
.eq-token-meter-swatch {
--_tone: var(--eq-ink);
}
.eq-token-meter-segment:nth-child(2),
.eq-token-meter-item:nth-child(2) .eq-token-meter-swatch {
--_tone: color-mix(in srgb, var(--eq-ink) 60%, transparent);
}
.eq-token-meter-segment:nth-child(3),
.eq-token-meter-item:nth-child(3) .eq-token-meter-swatch {
--_tone: color-mix(in srgb, var(--eq-ink) 36%, transparent);
}
.eq-token-meter-segment:nth-child(n + 4),
.eq-token-meter-item:nth-child(n + 4) .eq-token-meter-swatch {
--_tone: color-mix(in srgb, var(--eq-ink) 20%, transparent);
}
.eq-token-meter-bar {
display: flex;
gap: 1px;
height: 8px;
overflow: hidden;
border-radius: var(--eq-radius-s);
background: var(--eq-tint);
}
.eq-token-meter[data-compact] .eq-token-meter-bar {
flex: 1;
min-width: var(--eq-space-6);
height: 4px;
}
/* Compact has no state line, so the number carries the warning. */
.eq-token-meter[data-compact][data-state="warn"] .eq-token-meter-figure {
color: var(--eq-warn);
}
.eq-token-meter[data-compact][data-state="over"] .eq-token-meter-figure {
color: var(--eq-danger);
}
.eq-token-meter[data-state="over"] .eq-token-meter-bar {
box-shadow: inset 0 0 0 1px var(--eq-danger);
}
.eq-token-meter-segment {
flex: 0 0 var(--_share);
min-width: 0;
background: var(--_tone);
transition:
flex-basis var(--eq-dur-move) var(--eq-spring),
background-color var(--eq-dur) var(--eq-ease);
}
.eq-token-meter-legend {
display: flex;
flex-wrap: wrap;
gap: var(--eq-space-1) var(--eq-space-3);
margin: 0;
padding: 0;
list-style: none;
font-size: var(--eq-text-xs);
}
.eq-token-meter-item {
display: inline-flex;
align-items: center;
gap: var(--eq-space-1);
white-space: nowrap;
}
.eq-token-meter-swatch {
flex: none;
width: 8px;
height: 8px;
border-radius: 2px;
background: var(--_tone);
}
.eq-token-meter-item[data-free] .eq-token-meter-swatch {
background: var(--eq-tint);
box-shadow: inset 0 0 0 1px var(--eq-line-strong);
}
.eq-token-meter-name {
color: var(--eq-ink-2);
}
.eq-token-meter-amount {
font-variant-numeric: tabular-nums;
font-weight: var(--eq-weight-strong);
}
.eq-token-meter-state {
display: flex;
align-items: center;
gap: var(--eq-space-1);
margin: 0;
color: var(--eq-ink-2);
font-size: var(--eq-text-xs);
}
.eq-token-meter-state::before {
content: "";
width: 6px;
height: 6px;
border-radius: var(--eq-radius-full);
background: var(--eq-ok);
}
.eq-token-meter[data-state="warn"] .eq-token-meter-state::before {
background: var(--eq-warn);
}
.eq-token-meter[data-state="over"] .eq-token-meter-state {
color: var(--eq-danger);
}
.eq-token-meter[data-state="over"] .eq-token-meter-state::before {
background: var(--eq-danger);
}
}