03 / 18
All 18 in 18 Tailwind CSS Settings Pages
- 01Tailwind Account Settings Page Layout
- 02Tailwind Billing and Plan Settings Page
- 03Tailwind Usage Page with Quota Meters
- 04Tailwind Invoice History Table
- 05Tailwind Payment Method Settings Card
- 06Tailwind Team Members Page with Roles
- 07Tailwind Invite Teammates Form
- 08Tailwind Roles and Permissions Matrix
- 09Tailwind API Keys Management Page
- 10Tailwind Two-Factor Authentication Setup
- 11Tailwind Active Sessions Settings Page
- 12Tailwind Notification Preferences Page
- 13Tailwind Connected Apps Settings Page
- 14Tailwind Appearance Settings Page
- 15Tailwind Data Export Request Page
- 16Tailwind Audit Log Activity Page
- 17Tailwind Webhooks Settings Page
- 18Tailwind Danger Zone Delete Account
Tailwind Usage Page with Quota Meters
A usage dashboard where limits read at a glance: a conic-gradient ring for API requests that animates in through a registered @property, a 14-day request chart with an end-of-cycle forecast, and five linear quota meters that turn amber at 80% and red past the limit — with a page-level overage banner that appears only when a meter is over.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section data-tsp03 class="tsp-03 group/use w-full min-h-svh bg-tsp03-bg px-4 py-10 font-tsp03-sans text-tsp03-ink antialiased sm:px-8 sm:py-14" aria-labelledby="tsp-03-h">
<div class="mx-auto grid w-full max-w-6xl gap-6">
<header class="flex flex-wrap items-end justify-between gap-4">
<div><h1 id="tsp-03-h" class="text-3xl font-semibold tracking-tight">Usage</h1><p class="mt-1 text-tsp03-mut">Billing period Oct 1 – Oct 30 · resets in 12 days</p></div>
<a href="#tsp-03-h" class="inline-flex min-h-11 items-center gap-2 rounded-xl bg-tsp03-ink px-4 text-sm font-semibold text-white shadow-sm transition hover:bg-tsp03-ink/90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tsp03-acc">Upgrade plan</a>
</header>
<div role="alert" class="hidden items-start gap-3 rounded-2xl border border-rose-200 bg-rose-50 p-4 text-sm text-rose-800 group-has-[[data-level=over]]/use:flex">
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mt-0.5 size-5 flex-none"><path d="M12 9v4M12 17h.01M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/></svg>
<p><strong class="font-semibold">You're over a plan limit.</strong> Overage on build minutes is billed at $0.008 / min. Raise the limit or upgrade to avoid throttling.</p>
</div>
<div class="grid gap-6 lg:grid-cols-[22rem_minmax(0,1fr)]">
<article class="grid content-start gap-6 rounded-3xl border border-tsp03-line bg-white p-6 shadow-[0_1px_2px_rgb(0_0_0/0.04)] sm:p-7" aria-labelledby="tsp-03-req">
<div class="flex items-center justify-between"><h2 id="tsp-03-req" class="font-semibold">API requests</h2><span class="rounded-full bg-tsp03-acc/10 px-2.5 py-1 text-xs font-semibold text-tsp03-acc">Pro · 1M / mo</span></div>
<div data-ring role="meter" aria-labelledby="tsp-03-req" aria-valuemin="0" aria-valuemax="1000000" aria-valuenow="0" data-used="742318" data-limit="1000000" class="relative grid aspect-square w-52 place-items-center justify-self-center rounded-full bg-[conic-gradient(var(--color-tsp03-acc)_calc(var(--tsp03-p)*1%),var(--color-tsp03-track)_0)] transition-[--tsp03-p] duration-[1400ms] ease-out motion-reduce:transition-none">
<span class="absolute inset-3.5 rounded-full bg-white shadow-[inset_0_2px_6px_rgb(0_0_0/0.06)]"></span>
<span class="relative grid justify-items-center"><span data-ring-pct class="text-4xl font-semibold tabular-nums tracking-tight">0%</span><span class="text-xs text-tsp03-mut">742,318 of 1,000,000</span></span>
</div>
<div>
<div class="flex items-end gap-1 h-16" aria-hidden="true">
<i class="h-[38%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[44%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[40%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[52%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[48%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[30%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[26%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[58%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[64%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[61%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[72%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[68%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[86%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-full flex-1 rounded-sm bg-tsp03-acc"></i>
</div>
<div class="mt-2 flex justify-between text-[11px] text-tsp03-mut"><span>Sep 24</span><span>Today</span></div>
</div>
<p class="flex items-start gap-2 rounded-2xl bg-amber-50 p-3 text-sm text-amber-900"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mt-0.5 size-4 flex-none"><path d="M3 17 9 11l4 4 8-8M15 7h6v6"/></svg><span>At this pace you'll reach <strong class="font-semibold">1.24M</strong> by Oct 30 — 24% over.</span></p>
</article>
<article class="rounded-3xl border border-tsp03-line bg-white p-6 shadow-[0_1px_2px_rgb(0_0_0/0.04)] sm:p-7" aria-labelledby="tsp-03-q">
<h2 id="tsp-03-q" class="font-semibold">Plan quotas</h2>
<div class="mt-2 grid divide-y divide-tsp03-line">
<div data-meter data-used="9" data-limit="12" data-unit="seats" class="group/m grid gap-2.5 py-5">
<div class="flex flex-wrap items-baseline justify-between gap-2"><span id="tsp-03-m1" class="font-medium">Seats</span><span class="text-sm tabular-nums"><strong data-val class="font-semibold"></strong> <span data-of class="text-tsp03-mut"></span></span></div>
<div role="meter" aria-labelledby="tsp-03-m1" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" class="h-2 overflow-hidden rounded-full bg-tsp03-track"><div class="h-full w-[calc(var(--tsp03-p,0)*1%)] max-w-full rounded-full bg-tsp03-acc transition-[width] duration-1000 ease-out group-data-[level=warn]/m:bg-amber-500 group-data-[level=over]/m:bg-rose-500 motion-reduce:transition-none"></div></div>
<p data-note class="text-xs text-tsp03-mut group-data-[level=warn]/m:text-amber-700 group-data-[level=over]/m:font-semibold group-data-[level=over]/m:text-rose-700"></p>
</div>
<div data-meter data-used="38.2" data-limit="100" data-unit="GB" class="group/m grid gap-2.5 py-5">
<div class="flex flex-wrap items-baseline justify-between gap-2"><span id="tsp-03-m2" class="font-medium">Storage</span><span class="text-sm tabular-nums"><strong data-val class="font-semibold"></strong> <span data-of class="text-tsp03-mut"></span></span></div>
<div role="meter" aria-labelledby="tsp-03-m2" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" class="h-2 overflow-hidden rounded-full bg-tsp03-track"><div class="h-full w-[calc(var(--tsp03-p,0)*1%)] max-w-full rounded-full bg-tsp03-acc transition-[width] duration-1000 ease-out group-data-[level=warn]/m:bg-amber-500 group-data-[level=over]/m:bg-rose-500 motion-reduce:transition-none"></div></div>
<p data-note class="text-xs text-tsp03-mut group-data-[level=warn]/m:text-amber-700 group-data-[level=over]/m:font-semibold group-data-[level=over]/m:text-rose-700"></p>
</div>
<div data-meter data-used="612" data-limit="1000" data-unit="GB" class="group/m grid gap-2.5 py-5">
<div class="flex flex-wrap items-baseline justify-between gap-2"><span id="tsp-03-m3" class="font-medium">Bandwidth</span><span class="text-sm tabular-nums"><strong data-val class="font-semibold"></strong> <span data-of class="text-tsp03-mut"></span></span></div>
<div role="meter" aria-labelledby="tsp-03-m3" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" class="h-2 overflow-hidden rounded-full bg-tsp03-track"><div class="h-full w-[calc(var(--tsp03-p,0)*1%)] max-w-full rounded-full bg-tsp03-acc transition-[width] duration-1000 ease-out group-data-[level=warn]/m:bg-amber-500 group-data-[level=over]/m:bg-rose-500 motion-reduce:transition-none"></div></div>
<p data-note class="text-xs text-tsp03-mut group-data-[level=warn]/m:text-amber-700 group-data-[level=over]/m:font-semibold group-data-[level=over]/m:text-rose-700"></p>
</div>
<div data-meter data-used="3120" data-limit="3000" data-unit="min" class="group/m grid gap-2.5 py-5">
<div class="flex flex-wrap items-baseline justify-between gap-2"><span id="tsp-03-m4" class="font-medium">Build minutes</span><span class="text-sm tabular-nums"><strong data-val class="font-semibold"></strong> <span data-of class="text-tsp03-mut"></span></span></div>
<div role="meter" aria-labelledby="tsp-03-m4" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" class="h-2 overflow-hidden rounded-full bg-tsp03-track"><div class="h-full w-[calc(var(--tsp03-p,0)*1%)] max-w-full rounded-full bg-tsp03-acc transition-[width] duration-1000 ease-out group-data-[level=warn]/m:bg-amber-500 group-data-[level=over]/m:bg-rose-500 motion-reduce:transition-none"></div></div>
<p data-note class="text-xs text-tsp03-mut group-data-[level=warn]/m:text-amber-700 group-data-[level=over]/m:font-semibold group-data-[level=over]/m:text-rose-700"></p>
</div>
<div data-meter data-used="41200" data-limit="50000" data-unit="credits" class="group/m grid gap-2.5 py-5">
<div class="flex flex-wrap items-baseline justify-between gap-2"><span id="tsp-03-m5" class="font-medium">AI credits</span><span class="text-sm tabular-nums"><strong data-val class="font-semibold"></strong> <span data-of class="text-tsp03-mut"></span></span></div>
<div role="meter" aria-labelledby="tsp-03-m5" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" class="h-2 overflow-hidden rounded-full bg-tsp03-track"><div class="h-full w-[calc(var(--tsp03-p,0)*1%)] max-w-full rounded-full bg-tsp03-acc transition-[width] duration-1000 ease-out group-data-[level=warn]/m:bg-amber-500 group-data-[level=over]/m:bg-rose-500 motion-reduce:transition-none"></div></div>
<p data-note class="text-xs text-tsp03-mut group-data-[level=warn]/m:text-amber-700 group-data-[level=over]/m:font-semibold group-data-[level=over]/m:text-rose-700"></p>
</div>
</div>
</article>
</div>
</div>
</section><section data-tsp03 class="tsp-03 group/use w-full min-h-svh bg-tsp03-bg px-4 py-10 font-tsp03-sans text-tsp03-ink antialiased sm:px-8 sm:py-14" aria-labelledby="tsp-03-h">
<div class="mx-auto grid w-full max-w-6xl gap-6">
<header class="flex flex-wrap items-end justify-between gap-4">
<div><h1 id="tsp-03-h" class="text-3xl font-semibold tracking-tight">Usage</h1><p class="mt-1 text-tsp03-mut">Billing period Oct 1 – Oct 30 · resets in 12 days</p></div>
<a href="#tsp-03-h" class="inline-flex min-h-11 items-center gap-2 rounded-xl bg-tsp03-ink px-4 text-sm font-semibold text-white shadow-sm transition hover:bg-tsp03-ink/90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tsp03-acc">Upgrade plan</a>
</header>
<div role="alert" class="hidden items-start gap-3 rounded-2xl border border-rose-200 bg-rose-50 p-4 text-sm text-rose-800 group-has-[[data-level=over]]/use:flex">
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mt-0.5 size-5 flex-none"><path d="M12 9v4M12 17h.01M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/></svg>
<p><strong class="font-semibold">You're over a plan limit.</strong> Overage on build minutes is billed at $0.008 / min. Raise the limit or upgrade to avoid throttling.</p>
</div>
<div class="grid gap-6 lg:grid-cols-[22rem_minmax(0,1fr)]">
<article class="grid content-start gap-6 rounded-3xl border border-tsp03-line bg-white p-6 shadow-[0_1px_2px_rgb(0_0_0/0.04)] sm:p-7" aria-labelledby="tsp-03-req">
<div class="flex items-center justify-between"><h2 id="tsp-03-req" class="font-semibold">API requests</h2><span class="rounded-full bg-tsp03-acc/10 px-2.5 py-1 text-xs font-semibold text-tsp03-acc">Pro · 1M / mo</span></div>
<div data-ring role="meter" aria-labelledby="tsp-03-req" aria-valuemin="0" aria-valuemax="1000000" aria-valuenow="0" data-used="742318" data-limit="1000000" class="relative grid aspect-square w-52 place-items-center justify-self-center rounded-full bg-[conic-gradient(var(--color-tsp03-acc)_calc(var(--tsp03-p)*1%),var(--color-tsp03-track)_0)] transition-[--tsp03-p] duration-[1400ms] ease-out motion-reduce:transition-none">
<span class="absolute inset-3.5 rounded-full bg-white shadow-[inset_0_2px_6px_rgb(0_0_0/0.06)]"></span>
<span class="relative grid justify-items-center"><span data-ring-pct class="text-4xl font-semibold tabular-nums tracking-tight">0%</span><span class="text-xs text-tsp03-mut">742,318 of 1,000,000</span></span>
</div>
<div>
<div class="flex items-end gap-1 h-16" aria-hidden="true">
<i class="h-[38%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[44%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[40%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[52%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[48%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[30%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[26%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[58%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[64%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[61%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[72%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[68%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[86%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-full flex-1 rounded-sm bg-tsp03-acc"></i>
</div>
<div class="mt-2 flex justify-between text-[11px] text-tsp03-mut"><span>Sep 24</span><span>Today</span></div>
</div>
<p class="flex items-start gap-2 rounded-2xl bg-amber-50 p-3 text-sm text-amber-900"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mt-0.5 size-4 flex-none"><path d="M3 17 9 11l4 4 8-8M15 7h6v6"/></svg><span>At this pace you'll reach <strong class="font-semibold">1.24M</strong> by Oct 30 — 24% over.</span></p>
</article>
<article class="rounded-3xl border border-tsp03-line bg-white p-6 shadow-[0_1px_2px_rgb(0_0_0/0.04)] sm:p-7" aria-labelledby="tsp-03-q">
<h2 id="tsp-03-q" class="font-semibold">Plan quotas</h2>
<div class="mt-2 grid divide-y divide-tsp03-line">
<div data-meter data-used="9" data-limit="12" data-unit="seats" class="group/m grid gap-2.5 py-5">
<div class="flex flex-wrap items-baseline justify-between gap-2"><span id="tsp-03-m1" class="font-medium">Seats</span><span class="text-sm tabular-nums"><strong data-val class="font-semibold"></strong> <span data-of class="text-tsp03-mut"></span></span></div>
<div role="meter" aria-labelledby="tsp-03-m1" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" class="h-2 overflow-hidden rounded-full bg-tsp03-track"><div class="h-full w-[calc(var(--tsp03-p,0)*1%)] max-w-full rounded-full bg-tsp03-acc transition-[width] duration-1000 ease-out group-data-[level=warn]/m:bg-amber-500 group-data-[level=over]/m:bg-rose-500 motion-reduce:transition-none"></div></div>
<p data-note class="text-xs text-tsp03-mut group-data-[level=warn]/m:text-amber-700 group-data-[level=over]/m:font-semibold group-data-[level=over]/m:text-rose-700"></p>
</div>
<div data-meter data-used="38.2" data-limit="100" data-unit="GB" class="group/m grid gap-2.5 py-5">
<div class="flex flex-wrap items-baseline justify-between gap-2"><span id="tsp-03-m2" class="font-medium">Storage</span><span class="text-sm tabular-nums"><strong data-val class="font-semibold"></strong> <span data-of class="text-tsp03-mut"></span></span></div>
<div role="meter" aria-labelledby="tsp-03-m2" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" class="h-2 overflow-hidden rounded-full bg-tsp03-track"><div class="h-full w-[calc(var(--tsp03-p,0)*1%)] max-w-full rounded-full bg-tsp03-acc transition-[width] duration-1000 ease-out group-data-[level=warn]/m:bg-amber-500 group-data-[level=over]/m:bg-rose-500 motion-reduce:transition-none"></div></div>
<p data-note class="text-xs text-tsp03-mut group-data-[level=warn]/m:text-amber-700 group-data-[level=over]/m:font-semibold group-data-[level=over]/m:text-rose-700"></p>
</div>
<div data-meter data-used="612" data-limit="1000" data-unit="GB" class="group/m grid gap-2.5 py-5">
<div class="flex flex-wrap items-baseline justify-between gap-2"><span id="tsp-03-m3" class="font-medium">Bandwidth</span><span class="text-sm tabular-nums"><strong data-val class="font-semibold"></strong> <span data-of class="text-tsp03-mut"></span></span></div>
<div role="meter" aria-labelledby="tsp-03-m3" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" class="h-2 overflow-hidden rounded-full bg-tsp03-track"><div class="h-full w-[calc(var(--tsp03-p,0)*1%)] max-w-full rounded-full bg-tsp03-acc transition-[width] duration-1000 ease-out group-data-[level=warn]/m:bg-amber-500 group-data-[level=over]/m:bg-rose-500 motion-reduce:transition-none"></div></div>
<p data-note class="text-xs text-tsp03-mut group-data-[level=warn]/m:text-amber-700 group-data-[level=over]/m:font-semibold group-data-[level=over]/m:text-rose-700"></p>
</div>
<div data-meter data-used="3120" data-limit="3000" data-unit="min" class="group/m grid gap-2.5 py-5">
<div class="flex flex-wrap items-baseline justify-between gap-2"><span id="tsp-03-m4" class="font-medium">Build minutes</span><span class="text-sm tabular-nums"><strong data-val class="font-semibold"></strong> <span data-of class="text-tsp03-mut"></span></span></div>
<div role="meter" aria-labelledby="tsp-03-m4" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" class="h-2 overflow-hidden rounded-full bg-tsp03-track"><div class="h-full w-[calc(var(--tsp03-p,0)*1%)] max-w-full rounded-full bg-tsp03-acc transition-[width] duration-1000 ease-out group-data-[level=warn]/m:bg-amber-500 group-data-[level=over]/m:bg-rose-500 motion-reduce:transition-none"></div></div>
<p data-note class="text-xs text-tsp03-mut group-data-[level=warn]/m:text-amber-700 group-data-[level=over]/m:font-semibold group-data-[level=over]/m:text-rose-700"></p>
</div>
<div data-meter data-used="41200" data-limit="50000" data-unit="credits" class="group/m grid gap-2.5 py-5">
<div class="flex flex-wrap items-baseline justify-between gap-2"><span id="tsp-03-m5" class="font-medium">AI credits</span><span class="text-sm tabular-nums"><strong data-val class="font-semibold"></strong> <span data-of class="text-tsp03-mut"></span></span></div>
<div role="meter" aria-labelledby="tsp-03-m5" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" class="h-2 overflow-hidden rounded-full bg-tsp03-track"><div class="h-full w-[calc(var(--tsp03-p,0)*1%)] max-w-full rounded-full bg-tsp03-acc transition-[width] duration-1000 ease-out group-data-[level=warn]/m:bg-amber-500 group-data-[level=over]/m:bg-rose-500 motion-reduce:transition-none"></div></div>
<p data-note class="text-xs text-tsp03-mut group-data-[level=warn]/m:text-amber-700 group-data-[level=over]/m:font-semibold group-data-[level=over]/m:text-rose-700"></p>
</div>
</div>
</article>
</div>
</div>
</section>@import "tailwindcss";
@theme {
--color-tsp03-bg: #f4f7fa;
--color-tsp03-bg: oklch(0.975 0.005 240);
--color-tsp03-ink: #0f1b2a;
--color-tsp03-ink: oklch(0.215 0.035 250);
--color-tsp03-mut: #5a6878;
--color-tsp03-mut: oklch(0.52 0.03 250);
--color-tsp03-line: #e2e8ef;
--color-tsp03-line: oklch(0.928 0.011 250);
--color-tsp03-track: #e9eef4;
--color-tsp03-track: oklch(0.945 0.01 250);
--color-tsp03-acc: #0ea5e9;
--color-tsp03-acc: oklch(0.685 0.148 237);
--font-tsp03-sans: 'Sora', system-ui, sans-serif;
}
/* A typed number is what lets the conic-gradient ring interpolate — utilities can't register one. */
@property --tsp03-p {
syntax: '<number>';
inherits: true;
initial-value: 0;
}
.tsp-03 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tsp03-bg: #f4f7fa;
--color-tsp03-bg: oklch(0.975 0.005 240);
--color-tsp03-ink: #0f1b2a;
--color-tsp03-ink: oklch(0.215 0.035 250);
--color-tsp03-mut: #5a6878;
--color-tsp03-mut: oklch(0.52 0.03 250);
--color-tsp03-line: #e2e8ef;
--color-tsp03-line: oklch(0.928 0.011 250);
--color-tsp03-track: #e9eef4;
--color-tsp03-track: oklch(0.945 0.01 250);
--color-tsp03-acc: #0ea5e9;
--color-tsp03-acc: oklch(0.685 0.148 237);
--font-tsp03-sans: 'Sora', system-ui, sans-serif;
}
/* A typed number is what lets the conic-gradient ring interpolate — utilities can't register one. */
@property --tsp03-p {
syntax: '<number>';
inherits: true;
initial-value: 0;
}
.tsp-03 {
width: 100%;
min-height: 100svh;
}(() => {
const root = document.querySelector('[data-tsp03]');
if (!root || root.dataset.wired) return;
root.dataset.wired = '1';
const fmt = (n) => Number(n).toLocaleString('en-US', { maximumFractionDigits: 1 });
const level = (p) => (p > 100 ? 'over' : p >= 80 ? 'warn' : 'ok');
const ring = root.querySelector('[data-ring]');
const meters = [...root.querySelectorAll('[data-meter]')];
meters.forEach((m) => {
const used = +m.dataset.used, limit = +m.dataset.limit, unit = m.dataset.unit;
const p = (used / limit) * 100;
m.dataset.level = level(p);
m.querySelector('[data-val]').textContent = fmt(used) + ' ' + unit;
m.querySelector('[data-of]').textContent = 'of ' + fmt(limit);
m.querySelector('[role=meter]').setAttribute('aria-valuenow', Math.round(p));
const note = m.querySelector('[data-note]');
if (p > 100) note.textContent = 'Over by ' + fmt(used - limit) + ' ' + unit + ' — overage applies';
else if (p >= 80) note.textContent = Math.round(p) + '% used — consider raising this limit';
else note.textContent = fmt(limit - used) + ' ' + unit + ' remaining';
});
const ringP = (+ring.dataset.used / +ring.dataset.limit) * 100;
ring.setAttribute('aria-valuenow', ring.dataset.used);
setTimeout(() => {
meters.forEach((m) => m.style.setProperty('--tsp03-p', ((+m.dataset.used / +m.dataset.limit) * 100).toFixed(1)));
ring.style.setProperty('--tsp03-p', ringP.toFixed(1));
}, 80);
const pct = root.querySelector('[data-ring-pct]');
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
const start = Date.now() + 80, dur = reduce ? 0 : 1400;
const tick = () => {
const k = dur ? Math.min(1, Math.max(0, Date.now() - start) / dur) : 1;
pct.textContent = Math.round(ringP * (1 - Math.pow(1 - k, 3))) + '%';
if (k < 1) setTimeout(tick, 16);
};
tick();
})();(() => {
const root = document.querySelector('[data-tsp03]');
if (!root || root.dataset.wired) return;
root.dataset.wired = '1';
const fmt = (n) => Number(n).toLocaleString('en-US', { maximumFractionDigits: 1 });
const level = (p) => (p > 100 ? 'over' : p >= 80 ? 'warn' : 'ok');
const ring = root.querySelector('[data-ring]');
const meters = [...root.querySelectorAll('[data-meter]')];
meters.forEach((m) => {
const used = +m.dataset.used, limit = +m.dataset.limit, unit = m.dataset.unit;
const p = (used / limit) * 100;
m.dataset.level = level(p);
m.querySelector('[data-val]').textContent = fmt(used) + ' ' + unit;
m.querySelector('[data-of]').textContent = 'of ' + fmt(limit);
m.querySelector('[role=meter]').setAttribute('aria-valuenow', Math.round(p));
const note = m.querySelector('[data-note]');
if (p > 100) note.textContent = 'Over by ' + fmt(used - limit) + ' ' + unit + ' — overage applies';
else if (p >= 80) note.textContent = Math.round(p) + '% used — consider raising this limit';
else note.textContent = fmt(limit - used) + ' ' + unit + ' remaining';
});
const ringP = (+ring.dataset.used / +ring.dataset.limit) * 100;
ring.setAttribute('aria-valuenow', ring.dataset.used);
setTimeout(() => {
meters.forEach((m) => m.style.setProperty('--tsp03-p', ((+m.dataset.used / +m.dataset.limit) * 100).toFixed(1)));
ring.style.setProperty('--tsp03-p', ringP.toFixed(1));
}, 80);
const pct = root.querySelector('[data-ring-pct]');
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
const start = Date.now() + 80, dur = reduce ? 0 : 1400;
const tick = () => {
const k = dur ? Math.min(1, Math.max(0, Date.now() - start) / dur) : 1;
pct.textContent = Math.round(ringP * (1 - Math.pow(1 - k, 3))) + '%';
if (k < 1) setTimeout(tick, 16);
};
tick();
})();Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>Compiles in the browser — perfect for a CodePen or a quick test, not for production.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-tsp03-bg: #f4f7fa;
--color-tsp03-bg: oklch(0.975 0.005 240);
--color-tsp03-ink: #0f1b2a;
--color-tsp03-ink: oklch(0.215 0.035 250);
--color-tsp03-mut: #5a6878;
--color-tsp03-mut: oklch(0.52 0.03 250);
--color-tsp03-line: #e2e8ef;
--color-tsp03-line: oklch(0.928 0.011 250);
--color-tsp03-track: #e9eef4;
--color-tsp03-track: oklch(0.945 0.01 250);
--color-tsp03-acc: #0ea5e9;
--color-tsp03-acc: oklch(0.685 0.148 237);
--font-tsp03-sans: 'Sora', system-ui, sans-serif;
}
/* A typed number is what lets the conic-gradient ring interpolate — utilities can't register one. */
@property --tsp03-p {
syntax: '<number>';
inherits: true;
initial-value: 0;
}
.tsp-03 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tsp03-bg: #f4f7fa;
--color-tsp03-bg: oklch(0.975 0.005 240);
--color-tsp03-ink: #0f1b2a;
--color-tsp03-ink: oklch(0.215 0.035 250);
--color-tsp03-mut: #5a6878;
--color-tsp03-mut: oklch(0.52 0.03 250);
--color-tsp03-line: #e2e8ef;
--color-tsp03-line: oklch(0.928 0.011 250);
--color-tsp03-track: #e9eef4;
--color-tsp03-track: oklch(0.945 0.01 250);
--color-tsp03-acc: #0ea5e9;
--color-tsp03-acc: oklch(0.685 0.148 237);
--font-tsp03-sans: 'Sora', system-ui, sans-serif;
}
/* A typed number is what lets the conic-gradient ring interpolate — utilities can't register one. */
@property --tsp03-p {
syntax: '<number>';
inherits: true;
initial-value: 0;
}
.tsp-03 {
width: 100%;
min-height: 100svh;
}<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@400..700&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@400..700&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- The CSS original's
[data-level=warn] .fillrules becamegroup-data-[level=warn]/m:variants on the fill; the script still writes only data and a custom property. - The
@propertyregistration stays CSS — it is the one thing here a utility can't mint. - UI upgrade from the CSS original: a 14-day request chart and a pace forecast ('1.24M by Oct 30') under the ring.
- UI upgrade: each meter writes a plain-English note — remaining, nearing the limit, or over by an exact amount — and a page-level banner appears through
group-has-[[data-level=over]]/use:.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Usage Page with Quota Meters
Source: https://codefronts.com/layouts/tailwind-css-settings-pages/tailwind-usage-page-with-quota-meters/
A usage dashboard where limits read at a glance: a conic-gradient ring for API requests that animates in through a registered @property, a 14-day request chart with an end-of-cycle forecast, and five linear quota meters that turn amber at 80% and red past the limit — with a page-level overage banner that appears only when a meter is over.
## HTML
```html
<section data-tsp03 class="tsp-03 group/use w-full min-h-svh bg-tsp03-bg px-4 py-10 font-tsp03-sans text-tsp03-ink antialiased sm:px-8 sm:py-14" aria-labelledby="tsp-03-h">
<div class="mx-auto grid w-full max-w-6xl gap-6">
<header class="flex flex-wrap items-end justify-between gap-4">
<div><h1 id="tsp-03-h" class="text-3xl font-semibold tracking-tight">Usage</h1><p class="mt-1 text-tsp03-mut">Billing period Oct 1 – Oct 30 · resets in 12 days</p></div>
<a href="#tsp-03-h" class="inline-flex min-h-11 items-center gap-2 rounded-xl bg-tsp03-ink px-4 text-sm font-semibold text-white shadow-sm transition hover:bg-tsp03-ink/90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tsp03-acc">Upgrade plan</a>
</header>
<div role="alert" class="hidden items-start gap-3 rounded-2xl border border-rose-200 bg-rose-50 p-4 text-sm text-rose-800 group-has-[[data-level=over]]/use:flex">
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mt-0.5 size-5 flex-none"><path d="M12 9v4M12 17h.01M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/></svg>
<p><strong class="font-semibold">You're over a plan limit.</strong> Overage on build minutes is billed at $0.008 / min. Raise the limit or upgrade to avoid throttling.</p>
</div>
<div class="grid gap-6 lg:grid-cols-[22rem_minmax(0,1fr)]">
<article class="grid content-start gap-6 rounded-3xl border border-tsp03-line bg-white p-6 shadow-[0_1px_2px_rgb(0_0_0/0.04)] sm:p-7" aria-labelledby="tsp-03-req">
<div class="flex items-center justify-between"><h2 id="tsp-03-req" class="font-semibold">API requests</h2><span class="rounded-full bg-tsp03-acc/10 px-2.5 py-1 text-xs font-semibold text-tsp03-acc">Pro · 1M / mo</span></div>
<div data-ring role="meter" aria-labelledby="tsp-03-req" aria-valuemin="0" aria-valuemax="1000000" aria-valuenow="0" data-used="742318" data-limit="1000000" class="relative grid aspect-square w-52 place-items-center justify-self-center rounded-full bg-[conic-gradient(var(--color-tsp03-acc)_calc(var(--tsp03-p)*1%),var(--color-tsp03-track)_0)] transition-[--tsp03-p] duration-[1400ms] ease-out motion-reduce:transition-none">
<span class="absolute inset-3.5 rounded-full bg-white shadow-[inset_0_2px_6px_rgb(0_0_0/0.06)]"></span>
<span class="relative grid justify-items-center"><span data-ring-pct class="text-4xl font-semibold tabular-nums tracking-tight">0%</span><span class="text-xs text-tsp03-mut">742,318 of 1,000,000</span></span>
</div>
<div>
<div class="flex items-end gap-1 h-16" aria-hidden="true">
<i class="h-[38%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[44%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[40%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[52%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[48%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[30%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[26%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[58%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[64%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[61%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[72%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[68%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[86%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-full flex-1 rounded-sm bg-tsp03-acc"></i>
</div>
<div class="mt-2 flex justify-between text-[11px] text-tsp03-mut"><span>Sep 24</span><span>Today</span></div>
</div>
<p class="flex items-start gap-2 rounded-2xl bg-amber-50 p-3 text-sm text-amber-900"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mt-0.5 size-4 flex-none"><path d="M3 17 9 11l4 4 8-8M15 7h6v6"/></svg><span>At this pace you'll reach <strong class="font-semibold">1.24M</strong> by Oct 30 — 24% over.</span></p>
</article>
<article class="rounded-3xl border border-tsp03-line bg-white p-6 shadow-[0_1px_2px_rgb(0_0_0/0.04)] sm:p-7" aria-labelledby="tsp-03-q">
<h2 id="tsp-03-q" class="font-semibold">Plan quotas</h2>
<div class="mt-2 grid divide-y divide-tsp03-line">
<div data-meter data-used="9" data-limit="12" data-unit="seats" class="group/m grid gap-2.5 py-5">
<div class="flex flex-wrap items-baseline justify-between gap-2"><span id="tsp-03-m1" class="font-medium">Seats</span><span class="text-sm tabular-nums"><strong data-val class="font-semibold"></strong> <span data-of class="text-tsp03-mut"></span></span></div>
<div role="meter" aria-labelledby="tsp-03-m1" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" class="h-2 overflow-hidden rounded-full bg-tsp03-track"><div class="h-full w-[calc(var(--tsp03-p,0)*1%)] max-w-full rounded-full bg-tsp03-acc transition-[width] duration-1000 ease-out group-data-[level=warn]/m:bg-amber-500 group-data-[level=over]/m:bg-rose-500 motion-reduce:transition-none"></div></div>
<p data-note class="text-xs text-tsp03-mut group-data-[level=warn]/m:text-amber-700 group-data-[level=over]/m:font-semibold group-data-[level=over]/m:text-rose-700"></p>
</div>
<div data-meter data-used="38.2" data-limit="100" data-unit="GB" class="group/m grid gap-2.5 py-5">
<div class="flex flex-wrap items-baseline justify-between gap-2"><span id="tsp-03-m2" class="font-medium">Storage</span><span class="text-sm tabular-nums"><strong data-val class="font-semibold"></strong> <span data-of class="text-tsp03-mut"></span></span></div>
<div role="meter" aria-labelledby="tsp-03-m2" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" class="h-2 overflow-hidden rounded-full bg-tsp03-track"><div class="h-full w-[calc(var(--tsp03-p,0)*1%)] max-w-full rounded-full bg-tsp03-acc transition-[width] duration-1000 ease-out group-data-[level=warn]/m:bg-amber-500 group-data-[level=over]/m:bg-rose-500 motion-reduce:transition-none"></div></div>
<p data-note class="text-xs text-tsp03-mut group-data-[level=warn]/m:text-amber-700 group-data-[level=over]/m:font-semibold group-data-[level=over]/m:text-rose-700"></p>
</div>
<div data-meter data-used="612" data-limit="1000" data-unit="GB" class="group/m grid gap-2.5 py-5">
<div class="flex flex-wrap items-baseline justify-between gap-2"><span id="tsp-03-m3" class="font-medium">Bandwidth</span><span class="text-sm tabular-nums"><strong data-val class="font-semibold"></strong> <span data-of class="text-tsp03-mut"></span></span></div>
<div role="meter" aria-labelledby="tsp-03-m3" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" class="h-2 overflow-hidden rounded-full bg-tsp03-track"><div class="h-full w-[calc(var(--tsp03-p,0)*1%)] max-w-full rounded-full bg-tsp03-acc transition-[width] duration-1000 ease-out group-data-[level=warn]/m:bg-amber-500 group-data-[level=over]/m:bg-rose-500 motion-reduce:transition-none"></div></div>
<p data-note class="text-xs text-tsp03-mut group-data-[level=warn]/m:text-amber-700 group-data-[level=over]/m:font-semibold group-data-[level=over]/m:text-rose-700"></p>
</div>
<div data-meter data-used="3120" data-limit="3000" data-unit="min" class="group/m grid gap-2.5 py-5">
<div class="flex flex-wrap items-baseline justify-between gap-2"><span id="tsp-03-m4" class="font-medium">Build minutes</span><span class="text-sm tabular-nums"><strong data-val class="font-semibold"></strong> <span data-of class="text-tsp03-mut"></span></span></div>
<div role="meter" aria-labelledby="tsp-03-m4" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" class="h-2 overflow-hidden rounded-full bg-tsp03-track"><div class="h-full w-[calc(var(--tsp03-p,0)*1%)] max-w-full rounded-full bg-tsp03-acc transition-[width] duration-1000 ease-out group-data-[level=warn]/m:bg-amber-500 group-data-[level=over]/m:bg-rose-500 motion-reduce:transition-none"></div></div>
<p data-note class="text-xs text-tsp03-mut group-data-[level=warn]/m:text-amber-700 group-data-[level=over]/m:font-semibold group-data-[level=over]/m:text-rose-700"></p>
</div>
<div data-meter data-used="41200" data-limit="50000" data-unit="credits" class="group/m grid gap-2.5 py-5">
<div class="flex flex-wrap items-baseline justify-between gap-2"><span id="tsp-03-m5" class="font-medium">AI credits</span><span class="text-sm tabular-nums"><strong data-val class="font-semibold"></strong> <span data-of class="text-tsp03-mut"></span></span></div>
<div role="meter" aria-labelledby="tsp-03-m5" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" class="h-2 overflow-hidden rounded-full bg-tsp03-track"><div class="h-full w-[calc(var(--tsp03-p,0)*1%)] max-w-full rounded-full bg-tsp03-acc transition-[width] duration-1000 ease-out group-data-[level=warn]/m:bg-amber-500 group-data-[level=over]/m:bg-rose-500 motion-reduce:transition-none"></div></div>
<p data-note class="text-xs text-tsp03-mut group-data-[level=warn]/m:text-amber-700 group-data-[level=over]/m:font-semibold group-data-[level=over]/m:text-rose-700"></p>
</div>
</div>
</article>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tsp03-bg: #f4f7fa;
--color-tsp03-bg: oklch(0.975 0.005 240);
--color-tsp03-ink: #0f1b2a;
--color-tsp03-ink: oklch(0.215 0.035 250);
--color-tsp03-mut: #5a6878;
--color-tsp03-mut: oklch(0.52 0.03 250);
--color-tsp03-line: #e2e8ef;
--color-tsp03-line: oklch(0.928 0.011 250);
--color-tsp03-track: #e9eef4;
--color-tsp03-track: oklch(0.945 0.01 250);
--color-tsp03-acc: #0ea5e9;
--color-tsp03-acc: oklch(0.685 0.148 237);
--font-tsp03-sans: 'Sora', system-ui, sans-serif;
}
/* A typed number is what lets the conic-gradient ring interpolate — utilities can't register one. */
@property --tsp03-p {
syntax: '<number>';
inherits: true;
initial-value: 0;
}
.tsp-03 {
width: 100%;
min-height: 100svh;
}
```
## JavaScript
```js
(() => {
const root = document.querySelector('[data-tsp03]');
if (!root || root.dataset.wired) return;
root.dataset.wired = '1';
const fmt = (n) => Number(n).toLocaleString('en-US', { maximumFractionDigits: 1 });
const level = (p) => (p > 100 ? 'over' : p >= 80 ? 'warn' : 'ok');
const ring = root.querySelector('[data-ring]');
const meters = [...root.querySelectorAll('[data-meter]')];
meters.forEach((m) => {
const used = +m.dataset.used, limit = +m.dataset.limit, unit = m.dataset.unit;
const p = (used / limit) * 100;
m.dataset.level = level(p);
m.querySelector('[data-val]').textContent = fmt(used) + ' ' + unit;
m.querySelector('[data-of]').textContent = 'of ' + fmt(limit);
m.querySelector('[role=meter]').setAttribute('aria-valuenow', Math.round(p));
const note = m.querySelector('[data-note]');
if (p > 100) note.textContent = 'Over by ' + fmt(used - limit) + ' ' + unit + ' — overage applies';
else if (p >= 80) note.textContent = Math.round(p) + '% used — consider raising this limit';
else note.textContent = fmt(limit - used) + ' ' + unit + ' remaining';
});
const ringP = (+ring.dataset.used / +ring.dataset.limit) * 100;
ring.setAttribute('aria-valuenow', ring.dataset.used);
setTimeout(() => {
meters.forEach((m) => m.style.setProperty('--tsp03-p', ((+m.dataset.used / +m.dataset.limit) * 100).toFixed(1)));
ring.style.setProperty('--tsp03-p', ringP.toFixed(1));
}, 80);
const pct = root.querySelector('[data-ring-pct]');
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
const start = Date.now() + 80, dur = reduce ? 0 : 1400;
const tick = () => {
const k = dur ? Math.min(1, Math.max(0, Date.now() - start) / dur) : 1;
pct.textContent = Math.round(ringP * (1 - Math.pow(1 - k, 3))) + '%';
if (k < 1) setTimeout(tick, 16);
};
tick();
})();
```Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Usage Page with Quota Meters
Source: https://codefronts.com/layouts/tailwind-css-settings-pages/tailwind-usage-page-with-quota-meters/
A usage dashboard where limits read at a glance: a conic-gradient ring for API requests that animates in through a registered @property, a 14-day request chart with an end-of-cycle forecast, and five linear quota meters that turn amber at 80% and red past the limit — with a page-level overage banner that appears only when a meter is over.
## HTML
```html
<section data-tsp03 class="tsp-03 group/use w-full min-h-svh bg-tsp03-bg px-4 py-10 font-tsp03-sans text-tsp03-ink antialiased sm:px-8 sm:py-14" aria-labelledby="tsp-03-h">
<div class="mx-auto grid w-full max-w-6xl gap-6">
<header class="flex flex-wrap items-end justify-between gap-4">
<div><h1 id="tsp-03-h" class="text-3xl font-semibold tracking-tight">Usage</h1><p class="mt-1 text-tsp03-mut">Billing period Oct 1 – Oct 30 · resets in 12 days</p></div>
<a href="#tsp-03-h" class="inline-flex min-h-11 items-center gap-2 rounded-xl bg-tsp03-ink px-4 text-sm font-semibold text-white shadow-sm transition hover:bg-tsp03-ink/90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tsp03-acc">Upgrade plan</a>
</header>
<div role="alert" class="hidden items-start gap-3 rounded-2xl border border-rose-200 bg-rose-50 p-4 text-sm text-rose-800 group-has-[[data-level=over]]/use:flex">
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mt-0.5 size-5 flex-none"><path d="M12 9v4M12 17h.01M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/></svg>
<p><strong class="font-semibold">You're over a plan limit.</strong> Overage on build minutes is billed at $0.008 / min. Raise the limit or upgrade to avoid throttling.</p>
</div>
<div class="grid gap-6 lg:grid-cols-[22rem_minmax(0,1fr)]">
<article class="grid content-start gap-6 rounded-3xl border border-tsp03-line bg-white p-6 shadow-[0_1px_2px_rgb(0_0_0/0.04)] sm:p-7" aria-labelledby="tsp-03-req">
<div class="flex items-center justify-between"><h2 id="tsp-03-req" class="font-semibold">API requests</h2><span class="rounded-full bg-tsp03-acc/10 px-2.5 py-1 text-xs font-semibold text-tsp03-acc">Pro · 1M / mo</span></div>
<div data-ring role="meter" aria-labelledby="tsp-03-req" aria-valuemin="0" aria-valuemax="1000000" aria-valuenow="0" data-used="742318" data-limit="1000000" class="relative grid aspect-square w-52 place-items-center justify-self-center rounded-full bg-[conic-gradient(var(--color-tsp03-acc)_calc(var(--tsp03-p)*1%),var(--color-tsp03-track)_0)] transition-[--tsp03-p] duration-[1400ms] ease-out motion-reduce:transition-none">
<span class="absolute inset-3.5 rounded-full bg-white shadow-[inset_0_2px_6px_rgb(0_0_0/0.06)]"></span>
<span class="relative grid justify-items-center"><span data-ring-pct class="text-4xl font-semibold tabular-nums tracking-tight">0%</span><span class="text-xs text-tsp03-mut">742,318 of 1,000,000</span></span>
</div>
<div>
<div class="flex items-end gap-1 h-16" aria-hidden="true">
<i class="h-[38%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[44%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[40%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[52%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[48%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[30%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[26%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[58%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[64%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[61%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[72%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[68%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-[86%] flex-1 rounded-sm bg-tsp03-acc/25"></i><i class="h-full flex-1 rounded-sm bg-tsp03-acc"></i>
</div>
<div class="mt-2 flex justify-between text-[11px] text-tsp03-mut"><span>Sep 24</span><span>Today</span></div>
</div>
<p class="flex items-start gap-2 rounded-2xl bg-amber-50 p-3 text-sm text-amber-900"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mt-0.5 size-4 flex-none"><path d="M3 17 9 11l4 4 8-8M15 7h6v6"/></svg><span>At this pace you'll reach <strong class="font-semibold">1.24M</strong> by Oct 30 — 24% over.</span></p>
</article>
<article class="rounded-3xl border border-tsp03-line bg-white p-6 shadow-[0_1px_2px_rgb(0_0_0/0.04)] sm:p-7" aria-labelledby="tsp-03-q">
<h2 id="tsp-03-q" class="font-semibold">Plan quotas</h2>
<div class="mt-2 grid divide-y divide-tsp03-line">
<div data-meter data-used="9" data-limit="12" data-unit="seats" class="group/m grid gap-2.5 py-5">
<div class="flex flex-wrap items-baseline justify-between gap-2"><span id="tsp-03-m1" class="font-medium">Seats</span><span class="text-sm tabular-nums"><strong data-val class="font-semibold"></strong> <span data-of class="text-tsp03-mut"></span></span></div>
<div role="meter" aria-labelledby="tsp-03-m1" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" class="h-2 overflow-hidden rounded-full bg-tsp03-track"><div class="h-full w-[calc(var(--tsp03-p,0)*1%)] max-w-full rounded-full bg-tsp03-acc transition-[width] duration-1000 ease-out group-data-[level=warn]/m:bg-amber-500 group-data-[level=over]/m:bg-rose-500 motion-reduce:transition-none"></div></div>
<p data-note class="text-xs text-tsp03-mut group-data-[level=warn]/m:text-amber-700 group-data-[level=over]/m:font-semibold group-data-[level=over]/m:text-rose-700"></p>
</div>
<div data-meter data-used="38.2" data-limit="100" data-unit="GB" class="group/m grid gap-2.5 py-5">
<div class="flex flex-wrap items-baseline justify-between gap-2"><span id="tsp-03-m2" class="font-medium">Storage</span><span class="text-sm tabular-nums"><strong data-val class="font-semibold"></strong> <span data-of class="text-tsp03-mut"></span></span></div>
<div role="meter" aria-labelledby="tsp-03-m2" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" class="h-2 overflow-hidden rounded-full bg-tsp03-track"><div class="h-full w-[calc(var(--tsp03-p,0)*1%)] max-w-full rounded-full bg-tsp03-acc transition-[width] duration-1000 ease-out group-data-[level=warn]/m:bg-amber-500 group-data-[level=over]/m:bg-rose-500 motion-reduce:transition-none"></div></div>
<p data-note class="text-xs text-tsp03-mut group-data-[level=warn]/m:text-amber-700 group-data-[level=over]/m:font-semibold group-data-[level=over]/m:text-rose-700"></p>
</div>
<div data-meter data-used="612" data-limit="1000" data-unit="GB" class="group/m grid gap-2.5 py-5">
<div class="flex flex-wrap items-baseline justify-between gap-2"><span id="tsp-03-m3" class="font-medium">Bandwidth</span><span class="text-sm tabular-nums"><strong data-val class="font-semibold"></strong> <span data-of class="text-tsp03-mut"></span></span></div>
<div role="meter" aria-labelledby="tsp-03-m3" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" class="h-2 overflow-hidden rounded-full bg-tsp03-track"><div class="h-full w-[calc(var(--tsp03-p,0)*1%)] max-w-full rounded-full bg-tsp03-acc transition-[width] duration-1000 ease-out group-data-[level=warn]/m:bg-amber-500 group-data-[level=over]/m:bg-rose-500 motion-reduce:transition-none"></div></div>
<p data-note class="text-xs text-tsp03-mut group-data-[level=warn]/m:text-amber-700 group-data-[level=over]/m:font-semibold group-data-[level=over]/m:text-rose-700"></p>
</div>
<div data-meter data-used="3120" data-limit="3000" data-unit="min" class="group/m grid gap-2.5 py-5">
<div class="flex flex-wrap items-baseline justify-between gap-2"><span id="tsp-03-m4" class="font-medium">Build minutes</span><span class="text-sm tabular-nums"><strong data-val class="font-semibold"></strong> <span data-of class="text-tsp03-mut"></span></span></div>
<div role="meter" aria-labelledby="tsp-03-m4" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" class="h-2 overflow-hidden rounded-full bg-tsp03-track"><div class="h-full w-[calc(var(--tsp03-p,0)*1%)] max-w-full rounded-full bg-tsp03-acc transition-[width] duration-1000 ease-out group-data-[level=warn]/m:bg-amber-500 group-data-[level=over]/m:bg-rose-500 motion-reduce:transition-none"></div></div>
<p data-note class="text-xs text-tsp03-mut group-data-[level=warn]/m:text-amber-700 group-data-[level=over]/m:font-semibold group-data-[level=over]/m:text-rose-700"></p>
</div>
<div data-meter data-used="41200" data-limit="50000" data-unit="credits" class="group/m grid gap-2.5 py-5">
<div class="flex flex-wrap items-baseline justify-between gap-2"><span id="tsp-03-m5" class="font-medium">AI credits</span><span class="text-sm tabular-nums"><strong data-val class="font-semibold"></strong> <span data-of class="text-tsp03-mut"></span></span></div>
<div role="meter" aria-labelledby="tsp-03-m5" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" class="h-2 overflow-hidden rounded-full bg-tsp03-track"><div class="h-full w-[calc(var(--tsp03-p,0)*1%)] max-w-full rounded-full bg-tsp03-acc transition-[width] duration-1000 ease-out group-data-[level=warn]/m:bg-amber-500 group-data-[level=over]/m:bg-rose-500 motion-reduce:transition-none"></div></div>
<p data-note class="text-xs text-tsp03-mut group-data-[level=warn]/m:text-amber-700 group-data-[level=over]/m:font-semibold group-data-[level=over]/m:text-rose-700"></p>
</div>
</div>
</article>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tsp03-bg: #f4f7fa;
--color-tsp03-bg: oklch(0.975 0.005 240);
--color-tsp03-ink: #0f1b2a;
--color-tsp03-ink: oklch(0.215 0.035 250);
--color-tsp03-mut: #5a6878;
--color-tsp03-mut: oklch(0.52 0.03 250);
--color-tsp03-line: #e2e8ef;
--color-tsp03-line: oklch(0.928 0.011 250);
--color-tsp03-track: #e9eef4;
--color-tsp03-track: oklch(0.945 0.01 250);
--color-tsp03-acc: #0ea5e9;
--color-tsp03-acc: oklch(0.685 0.148 237);
--font-tsp03-sans: 'Sora', system-ui, sans-serif;
}
/* A typed number is what lets the conic-gradient ring interpolate — utilities can't register one. */
@property --tsp03-p {
syntax: '<number>';
inherits: true;
initial-value: 0;
}
.tsp-03 {
width: 100%;
min-height: 100svh;
}
```
## JavaScript
```js
(() => {
const root = document.querySelector('[data-tsp03]');
if (!root || root.dataset.wired) return;
root.dataset.wired = '1';
const fmt = (n) => Number(n).toLocaleString('en-US', { maximumFractionDigits: 1 });
const level = (p) => (p > 100 ? 'over' : p >= 80 ? 'warn' : 'ok');
const ring = root.querySelector('[data-ring]');
const meters = [...root.querySelectorAll('[data-meter]')];
meters.forEach((m) => {
const used = +m.dataset.used, limit = +m.dataset.limit, unit = m.dataset.unit;
const p = (used / limit) * 100;
m.dataset.level = level(p);
m.querySelector('[data-val]').textContent = fmt(used) + ' ' + unit;
m.querySelector('[data-of]').textContent = 'of ' + fmt(limit);
m.querySelector('[role=meter]').setAttribute('aria-valuenow', Math.round(p));
const note = m.querySelector('[data-note]');
if (p > 100) note.textContent = 'Over by ' + fmt(used - limit) + ' ' + unit + ' — overage applies';
else if (p >= 80) note.textContent = Math.round(p) + '% used — consider raising this limit';
else note.textContent = fmt(limit - used) + ' ' + unit + ' remaining';
});
const ringP = (+ring.dataset.used / +ring.dataset.limit) * 100;
ring.setAttribute('aria-valuenow', ring.dataset.used);
setTimeout(() => {
meters.forEach((m) => m.style.setProperty('--tsp03-p', ((+m.dataset.used / +m.dataset.limit) * 100).toFixed(1)));
ring.style.setProperty('--tsp03-p', ringP.toFixed(1));
}, 80);
const pct = root.querySelector('[data-ring-pct]');
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
const start = Date.now() + 80, dur = reduce ? 0 : 1400;
const tick = () => {
const k = dur ? Math.min(1, Math.max(0, Date.now() - start) / dur) : 1;
pct.textContent = Math.round(ringP * (1 - Math.pow(1 - k, 3))) + '%';
if (k < 1) setTimeout(tick, 16);
};
tick();
})();
```How this works
Numbers live in the markup. Every meter carries data-used and data-limit. A twenty-line script computes the percentage, writes it to --tsp03-p, sets aria-valuenow and a data-level of ok, warn or over. It never touches a colour.
The ring animates because the property is typed. A plain custom property can't interpolate inside a gradient, so the stylesheet registers --tsp03-p as a <number> — one of the few things utilities can't express. Linear bars simply transition width. Under motion-reduce: both jump to their final value.
Make it yours
- Change the warn threshold in the script's
level()function — the colours follow automatically. - Add a meter: copy one
data-meterblock and set itsdata-used,data-limitanddata-unit. - Swap the ring accent with
--color-tsp03-acc; the track colour is--color-tsp03-track.
Gotchas — read before shipping
- Without the
@propertyregistration the ring snaps instead of sweeping — keep that block in the stylesheet. role=meterneedsaria-valuemin,aria-valuemaxand a label, or screen readers announce a bare number.- Clamp bar widths with
max-w-full; over-limit percentages exceed 100.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
@property landed in Firefox 128, matching Tailwind v4's own floor. Without JS the meters render empty but the numbers in the labels stay correct.