Skip to main content
WireKit
Copy for LLM

Usage Meter

A meter for showing how much of a plan limit has been consumed — seats, API calls, storage, projects. It pairs a labeled bar with a used / limit (%) readout, switches color as usage approaches the limit, and conveys the over-limit state by text (not color alone). Two sub-components round out the SaaS billing surface: usage-meter.panel (a responsive grid of meters) and usage-meter.gate (a plan-paywall wrapper that disables an action once the limit is reached).

The bar itself is a composed <x-wirekit::progress>, so it inherits the role="progressbar" WCAG contract for free.

Basic Usage

A single usage meter at 60%
Projects 6 / 10 projects (60%)

The readout shows 6 / 10 projects (60%). Pass used and limit from your Livewire component — the meter never computes usage itself.

Threshold States

As usage climbs, the intent shifts primary → warning → danger. Crossing the warn threshold (default 0.8) adds an "Approaching limit" line; reaching or exceeding the limit replaces it with "Over limit". Both states are spelled out in words so the signal never depends on color.

Ok, approaching, and over-limit meters
Storage — comfortable 40 / 100 GB (40%)
Storage — approaching 90 / 100 GB (90%)
Approaching limit
Storage — over limit 112 / 100 GB (112%)
Over limit

The over-limit meter shows the true overage (112%) in the readout while the bar clamps to 100% — the number tells the real story, the bar stays in bounds.

Unlimited Tier

Pass :limit="null" for an unlimited plan. The bar is omitted (there is no meaningful fill) and the readout reads Unlimited.

An unlimited plan tier
Team members 42 members · Unlimited

Reset Cadence

A period of day / week / month / year renders a subtle reset note. An explicit resetAt string overrides the derived note for an exact date.

Reset cadence hints
API calls 7,200 / 10,000 (72%)
Resets monthly
Exports 3 / 5 (60%)
Resets Jan 1, 2027

Upgrade Call To Action

The action slot hangs an upgrade prompt off a meter — handy the moment a plan is nearing its ceiling.

Meter with an upgrade prompt
Seats 5 / 5 seats (100%)
Over limit

Panel — A Grid Of Meters

usage-meter.panel lays out several meters in a responsive grid (1, 2, or 3 columns; collapses to a single column on small screens). Give it a title and it becomes a labeled group.

A plan-usage dashboard panel
Plan usage this month
Projects 8 / 10 projects (80%)
Approaching limit Resets monthly
Seats 3 / 5 seats (60%)
API calls 9,400 / 10,000 (94%)
Approaching limit Resets monthly
Storage 112 / 100 GB (112%)
Over limit

Gate — Plan Paywall

usage-meter.gate wraps an action that should be blocked once a limit is reached. While under limit, it renders its content untouched. When over is true, the content is dimmed and made inert (skipped by assistive tech and unclickable), and the reason plus an optional upgrade action slot explain why — and how to lift the gate.

A gated action at the plan ceiling
You've reached your 10-project limit. Upgrade plan

Livewire Integration

Bind used and limit to your component's state. As usage updates, the meter re-renders with the right threshold intent automatically:

{{-- 1. The meter reads usage straight off the component --}}
<x-wirekit::usage-meter
    label="API calls"
    :used="$usage['calls']"
    :limit="$plan->callLimit"
    unit="calls"
    period="month"
/>

{{-- 2. Gate the action behind the limit --}}
<x-wirekit::usage-meter.gate
    :over="$usage['projects'] >= $plan->projectLimit"
    reason="You've reached your project limit."
>
    <x-wirekit::button wire:click="createProject">New project</x-wirekit::button>
    <x-slot:action>
        <x-wirekit::button surface="outline" size="sm" href="{{ route('billing') }}">Upgrade</x-wirekit::button>
    </x-slot:action>
</x-wirekit::usage-meter.gate>
// 3. Supply usage + limits from the server — never recompute in the view
public array $usage = ['calls' => 7200, 'projects' => 10];

public function createProject(): void
{
    // The gate already blocks the click in the UI; re-check on the server too.
    abort_if($this->usage['projects'] >= $this->plan->projectLimit, 403);
    // …create…
}

Props

usage-meter

Prop Type Default Description
label string|null null Visible metric name above the bar
used numeric 0 Current usage (supplied by the app)
limit numeric|null null Plan limit; null = unlimited tier
unit string|null null Unit suffix in the readout (seats, GB)
period string|null null Reset cadence: day / week / month / year
resetAt string|null null Explicit reset note (overrides period)
warn float 0.8 Ratio at which the warning band starts
danger float 1.0 Ratio at which the danger/over band starts
showValue bool true Show the used / limit (%) readout
scope string|null null Scoped personalization name

usage-meter.panel

Prop Type Default Description
title string|null null Optional group heading above the grid
columns int 1 Column count on wider viewports: 1, 2, 3
scope string|null null Scoped personalization name

usage-meter.gate

Prop Type Default Description
over bool false true blocks the action and shows the reason
reason string|null null Why the action is blocked (paired with aria-disabled)
scope string|null null Scoped personalization name

Accessibility

  • The bar is a composed <x-wirekit::progress>, so it carries role="progressbar" with aria-valuenow / aria-valuemin / aria-valuemax. Screen readers announce the current usage.
  • A visible label is linked to the bar via aria-labelledby; without one, the bar falls back to aria-label="Usage".
  • The over-limit and approaching-limit states are conveyed in words, never by color alone — the colored token is redundant reinforcement on top of the text. This satisfies WCAG 1.4.1 (Use of Color).
  • usage-meter.gate marks the gated action aria-disabled="true" and inert, so it is skipped by assistive tech and cannot receive focus or clicks. The reason text is the accessible explanation that pairs with the disabled state.

Keyboard Interaction

The meter and panel are presentational and do not respond to keyboard input. The gate's wrapped action is inert while over limit, so keyboard users tab straight past it to the upgrade CTA.

Pitfalls

  • Supply used and limit from the server. The meter never recomputes usage — it renders exactly what you pass. Compute the usage in your Livewire component (or a cached aggregate) and bind it.
  • The gate is a UI affordance, not a security boundary. Always re-check the limit on the server in the action handler — a determined user can bypass any client-side disable.
  • Use :limit="null" for unlimited, not a huge number. A sentinel like 999999 renders a misleading near-empty bar; null renders the proper Unlimited tier with no bar.

Design Tokens

Element Token
Label color --color-wk-text
Readout color --color-wk-text-muted
Percentage / reset note --color-wk-text-subtle
"Over limit" text --color-wk-danger-text
"Approaching limit" text --color-wk-warning-text
Bar fill (under warn) --color-wk-accent
Bar fill (warning band) --color-wk-warning
Bar fill (danger / over) --color-wk-danger
Panel grid gap --space-wk-md
Gate disabled opacity --opacity-wk-disabled

Customization

Override the threshold defaults without publishing views via config/wirekit.php:

'components' => [
    'usage-meter' => [
        'warn' => 0.75,   // start the warning band at 75%
        'danger' => 0.95, // start the danger band at 95%
    ],
],

Further Reading