Component
Usage Meter
Dark, minimal plan-usage panel with four accessible quota meters (bandwidth, invocations, image optimization, build minutes). Meters hydrate from data-used/data-limit attributes, animate to their values, and flag near-limit state in text — ideal for billing and account settings pages.
- Component
- Tailwind
- HTML
- MIT
Preview
Live component
9:41 100%
devsnips.dev/library/tailwind/components/progress/usage-meter/ fluid · no fixed width 100%
Install
Add to your project
npx devsnips add Tailwind/Components/Progress/usage-meter Tailwind/Components/Progress/usage-meter <!--
Snippet Name: Usage Meter
Description: Dark, minimal plan-usage panel with accessible quota meters. Each meter hydrates from data-used / data-limit attributes, animates to its value, and flags near-limit state in text.
Author: DevSnips Contributors
Usage Example: Drop into a billing or account settings page. Set data-used, data-limit, and data-unit on each [data-meter] row; the scoped script formats values, sets ARIA, and animates the fills.
-->
<section
class="w-full max-w-2xl rounded-lg border border-white/10 bg-[#0a0a0a] p-6 text-white sm:p-8"
data-usage-meter="usage-meter"
aria-labelledby="usage-meter-heading"
>
<header class="flex flex-wrap items-start justify-between gap-4">
<div>
<p class="text-[11px] font-medium uppercase tracking-widest text-white/50">Plan usage</p>
<h2 id="usage-meter-heading" class="mt-1.5 text-lg font-medium tracking-tight text-white">
Current billing cycle
</h2>
<p class="mt-1 text-xs text-white/50">
Aug 1 – Aug 31, 2026 ·
<span data-usage-updated>Updated 4 minutes ago</span>
</p>
</div>
<div class="flex items-center gap-3">
<span class="rounded-md bg-[#50e3c2]/10 px-2 py-1 text-[11px] font-medium uppercase tracking-widest text-[#50e3c2] ring-1 ring-inset ring-[#50e3c2]/30">
Pro
</span>
<button
type="button"
data-usage-refresh
aria-label="Refresh usage data"
class="inline-flex h-8 w-8 items-center justify-center rounded-md text-white/50 transition-colors duration-150 hover:bg-white/5 hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#50e3c2]"
>
<svg data-usage-refresh-icon class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.582m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
</svg>
</button>
</div>
</header>
<ul role="list" class="mt-6 divide-y divide-white/10 border-y border-white/10" aria-label="Usage by resource">
<li class="py-4" data-meter data-name="Bandwidth" data-used="812" data-limit="1000" data-unit="GB">
<div class="flex items-baseline justify-between gap-4">
<p class="text-sm font-medium text-white">Bandwidth</p>
<p class="text-sm tabular-nums text-white/50">
<span data-meter-value class="text-white">812</span> of <span data-meter-limit>1,000</span> GB
<span data-meter-percent class="ml-2 text-xs text-white/50">81%</span>
</p>
</div>
<div
class="mt-2.5 h-1 w-full overflow-hidden rounded-full bg-white/10"
role="progressbar"
aria-label="Bandwidth"
aria-valuemin="0"
aria-valuemax="1000"
aria-valuenow="812"
aria-valuetext="812 of 1,000 GB used, 81 percent"
>
<div data-meter-fill class="h-full w-[81%] rounded-full bg-[#50e3c2] transition-[width] duration-700 ease-out"></div>
</div>
<p data-meter-note class="mt-2 hidden items-center gap-1.5 text-xs font-medium text-white" aria-live="polite">
<span class="h-1.5 w-1.5 rounded-full bg-[#50e3c2]" aria-hidden="true"></span>
Approaching limit — <a href="#" class="underline decoration-white/40 underline-offset-2 transition-colors duration-150 hover:decoration-[#50e3c2] hover:text-[#50e3c2] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#50e3c2]">upgrade plan</a>
</p>
</li>
<li class="py-4" data-meter data-name="Function invocations" data-used="8214930" data-limit="10000000" data-unit="invocations">
<div class="flex items-baseline justify-between gap-4">
<p class="text-sm font-medium text-white">Function invocations</p>
<p class="text-sm tabular-nums text-white/50">
<span data-meter-value class="text-white">8,214,930</span> of <span data-meter-limit>10,000,000</span> invocations
<span data-meter-percent class="ml-2 text-xs text-white/50">82%</span>
</p>
</div>
<div
class="mt-2.5 h-1 w-full overflow-hidden rounded-full bg-white/10"
role="progressbar"
aria-label="Function invocations"
aria-valuemin="0"
aria-valuemax="10000000"
aria-valuenow="8214930"
aria-valuetext="8,214,930 of 10,000,000 invocations used, 82 percent"
>
<div data-meter-fill class="h-full w-[82%] rounded-full bg-[#50e3c2] transition-[width] duration-700 ease-out"></div>
</div>
<p data-meter-note class="mt-2 hidden items-center gap-1.5 text-xs font-medium text-white" aria-live="polite">
<span class="h-1.5 w-1.5 rounded-full bg-[#50e3c2]" aria-hidden="true"></span>
Approaching limit — <a href="#" class="underline decoration-white/40 underline-offset-2 transition-colors duration-150 hover:decoration-[#50e3c2] hover:text-[#50e3c2] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#50e3c2]">upgrade plan</a>
</p>
</li>
<li class="py-4" data-meter data-name="Image optimization" data-used="9412" data-limit="10000" data-unit="transformations">
<div class="flex items-baseline justify-between gap-4">
<p class="text-sm font-medium text-white">Image optimization</p>
<p class="text-sm tabular-nums text-white/50">
<span data-meter-value class="text-white">9,412</span> of <span data-meter-limit>10,000</span> transformations
<span data-meter-percent class="ml-2 text-xs text-white/50">94%</span>
</p>
</div>
<div
class="mt-2.5 h-1 w-full overflow-hidden rounded-full bg-white/10"
role="progressbar"
aria-label="Image optimization"
aria-valuemin="0"
aria-valuemax="10000"
aria-valuenow="9412"
aria-valuetext="9,412 of 10,000 transformations used, 94 percent"
>
<div data-meter-fill class="h-full w-[94%] rounded-full bg-[#50e3c2] transition-[width] duration-700 ease-out"></div>
</div>
<p data-meter-note class="mt-2 flex items-center gap-1.5 text-xs font-medium text-white" aria-live="polite">
<span class="h-1.5 w-1.5 rounded-full bg-[#50e3c2]" aria-hidden="true"></span>
Approaching limit — <a href="#" class="underline decoration-white/40 underline-offset-2 transition-colors duration-150 hover:decoration-[#50e3c2] hover:text-[#50e3c2] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#50e3c2]">upgrade plan</a>
</p>
</li>
<li class="py-4" data-meter data-name="Build execution" data-used="187" data-limit="6000" data-unit="minutes">
<div class="flex items-baseline justify-between gap-4">
<p class="text-sm font-medium text-white">Build execution</p>
<p class="text-sm tabular-nums text-white/50">
<span data-meter-value class="text-white">187</span> of <span data-meter-limit>6,000</span> minutes
<span data-meter-percent class="ml-2 text-xs text-white/50">3%</span>
</p>
</div>
<div
class="mt-2.5 h-1 w-full overflow-hidden rounded-full bg-white/10"
role="progressbar"
aria-label="Build execution"
aria-valuemin="0"
aria-valuemax="6000"
aria-valuenow="187"
aria-valuetext="187 of 6,000 minutes used, 3 percent"
>
<div data-meter-fill class="h-full w-[3%] rounded-full bg-[#50e3c2] transition-[width] duration-700 ease-out"></div>
</div>
<p data-meter-note class="mt-2 hidden items-center gap-1.5 text-xs font-medium text-white" aria-live="polite">
<span class="h-1.5 w-1.5 rounded-full bg-[#50e3c2]" aria-hidden="true"></span>
Approaching limit — <a href="#" class="underline decoration-white/40 underline-offset-2 transition-colors duration-150 hover:decoration-[#50e3c2] hover:text-[#50e3c2] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#50e3c2]">upgrade plan</a>
</p>
</li>
</ul>
<footer class="mt-6 flex flex-wrap items-center justify-between gap-3">
<p class="text-xs text-white/50">Cycle resets in 8 days. Usage is measured across all projects in this team.</p>
<a
href="#"
class="inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-sm font-medium text-[#50e3c2] transition-colors duration-150 hover:bg-white/5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#50e3c2]"
>
View detailed usage
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7"/>
</svg>
</a>
</footer>
</section>
<script>
(function () {
const root = document.currentScript.closest('[data-usage-meter]');
if (!root) return;
const NEAR_LIMIT = 90;
const numberFormat = new Intl.NumberFormat('en-US');
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
function hydrateMeter(meter, animate) {
const used = Number(meter.dataset.used) || 0;
const limit = Number(meter.dataset.limit) || 100;
const unit = meter.dataset.unit || '';
const name = meter.dataset.name || 'Usage';
const percent = Math.max(0, Math.min(100, Math.round((used / limit) * 100)));
const fill = meter.querySelector('[data-meter-fill]');
const valueEl = meter.querySelector('[data-meter-value]');
const limitEl = meter.querySelector('[data-meter-limit]');
const percentEl = meter.querySelector('[data-meter-percent]');
const bar = meter.querySelector('[role="progressbar"]');
const note = meter.querySelector('[data-meter-note]');
valueEl.textContent = numberFormat.format(used);
limitEl.textContent = numberFormat.format(limit);
percentEl.textContent = percent + '%';
bar.setAttribute('aria-valuemax', String(limit));
bar.setAttribute('aria-valuenow', String(used));
bar.setAttribute(
'aria-valuetext',
numberFormat.format(used) + ' of ' + numberFormat.format(limit) + ' ' + unit + ' used, ' + percent + ' percent'
);
if (percent >= NEAR_LIMIT) {
note.classList.remove('hidden');
note.classList.add('flex');
} else {
note.classList.add('hidden');
note.classList.remove('flex');
}
const target = percent + '%';
if (animate && !reduceMotion.matches) {
fill.style.width = '0%';
requestAnimationFrame(function () {
requestAnimationFrame(function () {
fill.style.width = target;
});
});
} else {
fill.style.width = target;
}
}
const meters = Array.prototype.slice.call(root.querySelectorAll('[data-meter]'));
const refreshButton = root.querySelector('[data-usage-refresh]');
const refreshIcon = root.querySelector('[data-usage-refresh-icon]');
const updatedLabel = root.querySelector('[data-usage-updated]');
// Hydrate on load with the entrance animation
meters.forEach(function (meter) { hydrateMeter(meter, true); });
// Refresh button re-runs hydration (swap in a real fetch for live data)
let refreshing = false;
refreshButton.addEventListener('click', function () {
if (refreshing) return;
refreshing = true;
refreshButton.setAttribute('aria-busy', 'true');
refreshButton.disabled = true;
if (!reduceMotion.matches) refreshIcon.classList.add('animate-spin');
window.setTimeout(function () {
meters.forEach(function (meter) { hydrateMeter(meter, true); });
updatedLabel.textContent = 'Updated just now';
refreshIcon.classList.remove('animate-spin');
refreshButton.removeAttribute('aria-busy');
refreshButton.disabled = false;
refreshing = false;
}, reduceMotion.matches ? 150 : 900);
});
})();
</script> # Usage Meter
Usage / quota meter.
## Usage
```bash
npx devsnips add Tailwind/Components/Progress/usage-meter
```
Copy from `code.html`.
## File Structure
`code.html` · `preview.html` · `metadata.json` · `README.md` 238 lines UTF-8 · LF · Spaces: 2
Continue browsing