UI Components Easy
Token Counter
Real-time token count indicator that estimates tokens as you type, with a visual limit bar and warning state. No libraries.
Open in Lab
MCP
vanilla-js
Targets: JS HTML
Code
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: #f9fafb;
min-height: 100vh;
padding: 40px 24px;
display: flex;
justify-content: center;
}
.demo {
width: 100%;
max-width: 560px;
}
.demo-title {
font-size: 20px;
font-weight: 800;
margin-bottom: 6px;
}
.demo-desc {
font-size: 14px;
color: #6b7280;
margin-bottom: 20px;
}
.tc-card {
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 16px;
overflow: hidden;
margin-bottom: 12px;
}
.tc-input {
width: 100%;
border: none;
outline: none;
padding: 16px;
font-size: 14px;
color: #111827;
font-family: inherit;
resize: vertical;
min-height: 120px;
line-height: 1.6;
}
.tc-input::placeholder {
color: #9ca3af;
}
.tc-stats {
display: flex;
border-top: 1px solid #f3f4f6;
border-bottom: 1px solid #f3f4f6;
}
.tc-stat {
flex: 1;
padding: 12px;
text-align: center;
border-right: 1px solid #f3f4f6;
}
.tc-stat:last-child {
border-right: none;
}
.tc-stat-val {
display: block;
font-size: 22px;
font-weight: 900;
color: #111827;
font-variant-numeric: tabular-nums;
}
.tc-stat-label {
font-size: 11px;
color: #9ca3af;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.tc-models {
padding: 16px;
display: flex;
flex-direction: column;
gap: 12px;
}
.tc-model {
display: flex;
flex-direction: column;
gap: 5px;
}
.tc-model-bar {
height: 6px;
background: #f3f4f6;
border-radius: 3px;
overflow: hidden;
}
.tc-model-fill {
height: 100%;
background: #6366f1;
border-radius: 3px;
width: 0%;
transition: width 0.2s, background 0.2s;
}
.tc-model-fill.warn {
background: #d97706;
}
.tc-model-fill.over {
background: #dc2626;
}
.tc-model-info {
display: flex;
justify-content: space-between;
}
.tc-model-name {
font-size: 12px;
font-weight: 600;
color: #374151;
}
.tc-model-limit {
font-size: 12px;
color: #9ca3af;
}
.tc-model-limit.warn {
color: #d97706;
}
.tc-model-limit.over {
color: #dc2626;
font-weight: 700;
}
.tc-presets {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.preset-btn {
padding: 7px 14px;
background: #f3f4f6;
border: none;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
color: #374151;
cursor: pointer;
transition: background 0.15s;
}
.preset-btn:hover {
background: #e5e7eb;
}const input = document.getElementById("tcInput");
const MODELS = [
{ fillId: "m1fill", limId: "m1lim", limit: 128000, label: "128K" },
{ fillId: "m2fill", limId: "m2lim", limit: 200000, label: "200K" },
{ fillId: "m3fill", limId: "m3lim", limit: 1000000, label: "1M" },
];
function estimateTokens(text) {
return Math.ceil(text.length / 4);
}
function formatK(n) {
return n >= 1000 ? (n / 1000).toFixed(1) + "K" : String(n);
}
function update() {
const text = input.value;
const chars = text.length;
const words = text.trim() ? text.trim().split(/\s+/).length : 0;
const tokens = estimateTokens(text);
document.getElementById("tcChars").textContent = chars.toLocaleString();
document.getElementById("tcWords").textContent = words.toLocaleString();
document.getElementById("tcTokens").textContent = tokens.toLocaleString();
MODELS.forEach(({ fillId, limId, limit, label }) => {
const pct = Math.min((tokens / limit) * 100, 100);
const fill = document.getElementById(fillId);
const lim = document.getElementById(limId);
fill.style.width = pct + "%";
const state = pct >= 100 ? "over" : pct >= 80 ? "warn" : "";
fill.className = "tc-model-fill" + (state ? ` ${state}` : "");
lim.textContent = `${formatK(tokens)} / ${label}`;
lim.className = "tc-model-limit" + (state ? ` ${state}` : "");
});
}
input.addEventListener("input", update);
const LOREM =
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. ";
document.querySelectorAll(".preset-btn[data-len]").forEach((btn) => {
btn.addEventListener("click", () => {
const len = parseInt(btn.dataset.len);
let text = "";
while (text.length < len) text += LOREM;
input.value = text.slice(0, len);
update();
});
});
document.getElementById("clearPreset").addEventListener("click", () => {
input.value = "";
update();
});<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Token Counter</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="demo">
<h2 class="demo-title">Token Counter</h2>
<p class="demo-desc">Type in the textarea below to see live token estimation.</p>
<div class="tc-card">
<textarea class="tc-input" id="tcInput" placeholder="Enter your prompt here…" rows="5"></textarea>
<div class="tc-stats">
<div class="tc-stat">
<span class="tc-stat-val" id="tcChars">0</span>
<span class="tc-stat-label">Characters</span>
</div>
<div class="tc-stat">
<span class="tc-stat-val" id="tcWords">0</span>
<span class="tc-stat-label">Words</span>
</div>
<div class="tc-stat">
<span class="tc-stat-val" id="tcTokens">0</span>
<span class="tc-stat-label">Tokens ~</span>
</div>
</div>
<div class="tc-models">
<div class="tc-model" id="m1">
<div class="tc-model-bar"><div class="tc-model-fill" id="m1fill"></div></div>
<div class="tc-model-info">
<span class="tc-model-name">GPT-4o</span>
<span class="tc-model-limit" id="m1lim">0 / 128K</span>
</div>
</div>
<div class="tc-model" id="m2">
<div class="tc-model-bar"><div class="tc-model-fill" id="m2fill"></div></div>
<div class="tc-model-info">
<span class="tc-model-name">Claude Sonnet</span>
<span class="tc-model-limit" id="m2lim">0 / 200K</span>
</div>
</div>
<div class="tc-model" id="m3">
<div class="tc-model-bar"><div class="tc-model-fill" id="m3fill"></div></div>
<div class="tc-model-info">
<span class="tc-model-name">Gemini 2.0</span>
<span class="tc-model-limit" id="m3lim">0 / 1M</span>
</div>
</div>
</div>
</div>
<div class="tc-presets">
<button class="preset-btn" data-len="100">Short prompt</button>
<button class="preset-btn" data-len="500">Medium prompt</button>
<button class="preset-btn" data-len="2000">Long context</button>
<button class="preset-btn" id="clearPreset">Clear</button>
</div>
</div>
<script src="script.js"></script>
</body>
</html>Live token counter that estimates token count from text input (approx. 4 chars/token), renders a segmented progress bar, and transitions to warning/error states as you approach the context limit.