Feedback and live
Usage meter
A bar or small ring that shows how much of a limit is left, where the numbers roll as they are spent, the fill turns amber when little is left and red when nothing is left, and an upgrade link opens only when it matters.
Live preview
Loading preview
A "Monthly credits" bar with a limit of 2,000 that resets on Oct 12, plus the ring variant beside a message box. The demo spends credits in steps (760, then 1,640, then 2,000) so the meter passes Low and reaches Out, and the upgrade link opens; Replay sets used back to 760 and runs the steps again.
Installation
Run this in your app after the one-time setup on the Getting started page.
$ npx @ganisigit/lunia-ds add usage-meterUsage
The demo above, as code.
'use client';
import * as React from 'react';
import { UsageMeter } from './usage-meter';
const LIMIT = 2000;
const RESET = new Date(2026, 9, 12);
// Credits spent in steps, so the meter passes Low and reaches Out.
const STEPS: [at: number, used: number][] = [
[1400, 1640],
[2900, 2000],
];
export default function UsageMeterDemo({ replay = 0 }: { replay?: number }) {
const [used, setUsed] = React.useState(760);
// Replay sets the credits back to 760 and spends them again.
React.useEffect(() => {
if (replay === 0) return;
setUsed(760);
const timers = STEPS.map(([at, value]) => window.setTimeout(() => setUsed(value), at));
return () => {
for (const t of timers) window.clearTimeout(t);
};
}, [replay]);
return (
<div className="flex w-full max-w-[400px] flex-col gap-8 rounded-xl border border-secondary bg-primary p-6">
<UsageMeter
used={used}
limit={LIMIT}
label="Monthly credits"
unit="credits"
resetAt={RESET}
onUpgrade={() => {}}
/>
<div className="flex flex-col gap-3 rounded-2xl border border-secondary bg-primary_alt p-4 shadow-xs">
<p className="text-sm text-tertiary">Write a message</p>
<div className="flex items-center justify-end gap-3">
<UsageMeter
variant="ring"
used={used}
limit={LIMIT}
label="Monthly credits"
unit="credits"
resetAt={RESET}
/>
</div>
</div>
</div>
);
}
API
Parts: root, label, count (number roll: left, of, limit), unit line (text morph), track, fill, reset line (text morph), upgrade link (opens when low or out), ring (ring variant: track circle, fill arc, percent).
| Prop | Type | Default | Description |
|---|---|---|---|
| used | number | - | Amount already used. Required. |
| limit | number | - | Total amount for the period. Required. |
| unit | string | "credits" | Plural name of what is counted, for example "credits" or "messages". |
| label | string | "Usage" | Name of the meter, shown above the bar and used as the accessible name. |
| resetAt | number | Date | undefined | When the amount resets (ms or Date); shows the reset line. |
| variant | "bar" | "ring" | "bar" | A full-width bar for settings and menus, or a 16px ring with a percent for toolbars. |
| lowAt | number | 0.2 | Share left (0 to 1) at which the meter turns amber and shows the upgrade link. |
| upgradeLabel | string | "Upgrade" | Text of the upgrade link. |
| Event | Description |
|---|---|
| upgrade | Fires when the upgrade link is pressed; the link only shows when this event has a listener. |
Motion
Every row names a token from the Motion page. The last column says what happens when the user asks for reduced motion.
| ID | Trigger | Element | Property | From → to | Timing | Reduced motion |
|---|---|---|---|---|---|---|
| UM1 | used changes | fill (bar) | width | old share → new share | duration-4xl (520)ease-settle | Resizes at once |
| UM2 | level changes (Normal, Low, Out) | fill (bar) | background-color | fg-primary → fg-warning-primary (Low) or fg-error-primary (Out) | duration-4xl (520)ease-settle | Swaps at once |
| UM3 | used or limit changes | count | number roll | old number → new number | duration-5xl (640)ease-out | Swaps at once |
| UM4 | level changes to or from Out | count + unit line | color | text-tertiary → text-error-primary | duration-lg (320)ease-out | Swaps at once |
| UM5 | level changes to Out | count | grid-template-columns (fold away) + opacity + filter blur | 1fr, 1, 0 → 0fr, 0, blur-swap | duration-3xl (460)ease-out | Hides at once |
| UM6 | level changes from Out (limit raised or reset) | count | grid-template-columns (fold open) + opacity + filter blur | 0fr, 0, blur-swap → 1fr, 1, 0 | duration-3xl (460)ease-out | Shows at once |
| UM7 | level changes to or from Out | unit line | text morph | "credits left" → "No credits left" | duration-3xl (460)ease-out | Swaps at once |
| UM8 | level changes to or from Out | reset line | text morph | "Resets Oct 12" → "You can use more on Oct 12." | duration-3xl (460)ease-out | Swaps at once |
| UM9 | level changes to Low or Out | upgrade link | grid-template-columns (opens beside the reset line) + opacity + filter blur | 0fr, 0, blur-swap → 1fr, 1, 0 | duration-3xl (460)ease-out | Shows at once |
| UM10 | level changes back to Normal | upgrade link | grid-template-columns (fold away) + opacity + filter blur | 1fr, 1, 0 → 0fr, 0, blur-swap | duration-3xl (460)ease-out | Hides at once |
| UM11 | used changes (ring variant) | fill arc | stroke-dashoffset | old share → new share | duration-4xl (520)ease-out | Resizes at once |
| UM12 | level changes (ring variant) | fill arc | stroke color | fg-primary → fg-warning-primary or fg-error-primary | duration-4xl (520)ease-out | Swaps at once |
| UM13 | used changes (ring variant) | percent | number roll | old percent → new percent | duration-5xl (640)ease-out | Swaps at once |
Accessibility
- The bar track (or the ring) has role meter with aria-label from label, aria-valuemin 0, aria-valuemax = limit, aria-valuenow = used, and aria-valuetext with the plain summary, for example "1,240 of 2,000 credits left" or "No credits left".
- The visible count and unit line are aria-hidden, because they repeat the meter's value text and the rolling digits would be read one by one.
- The ring variant shows the summary and the reset date in a tooltip-style title, because it has no room for text.
- The upgrade link is inert and aria-hidden while it is folded away, so it is not in the tab order at the Normal level.
- Color is never the only signal: at the Out level the words change to "No credits left" and the reset line says when more is available.
- Reduced motion: the fill, numbers and folds change at once and the text swaps at once.