Quota banner
Tells people they are close to or out of their limit, as a card or a slim strip above the composer.
Loading preview…
Install
- 1
Run this command in your Tailwind CSS project. It adds
components/ai/quota-banner-warning.tsxand installs its dependencies.$ npx shadcn@latest add https://gradientdeck.com/ai-components/r/quota-banner-warning.json
Usage & API
Example
import { QuotaWarning } from "@/components/ai/quota-banner-warning"
<QuotaWarning
percent={1}
resetsAt="…"
/>Props QuotaWarning
| Prop | Type & description | Required |
|---|---|---|
percent | number | Required |
period | string | Optional |
resetsAt | string | Required |
onDismiss | () => void | Optional |
onViewUsage | () => void | Optional |
Dependencies
- React
- Tailwind CSS
- motion
- lucide-react
Implementation notes
QuotaWarning requires percent and resetsAt. period, onDismiss, and onViewUsage are optional. The icon is a triangle alert in an amber tile, and a hairline amber gradient runs along the top edge. The card leaves padding on the right so the dismiss control does not cover the title. The fill is a sand-to-amber gradient, animated with scaleX unless reduced motion is set. The track hides overflow, so a percent past 100 clips the fill even though the number beside it does not clip. Both dismiss controls call the same callback. You do not get a separate event for the X. The demo does not pass onViewUsage, so "See usage" renders and does nothing until you wire it, which is the natural place to open Usage meter. There is no stored "already dismissed" flag inside the file. Hiding it is your state, the way the demo's open flag unmounts the card.
Example
import { QuotaReached } from "@/components/ai/quota-banner-reached"
<QuotaReached
resetsIn={1}
resetsAt="…"
/>Props QuotaReached
| Prop | Type & description | Required |
|---|---|---|
resetsIn | numberSeconds until the limit resets. | Required |
resetsAt | string | Required |
plan | string | Optional |
onUpgrade | () => void | Optional |
onNotify | () => void | Optional |
notifying | boolean | Optional |
Dependencies
- React
- Tailwind CSS
- lucide-react
Implementation notes
QuotaReached requires resetsIn and resetsAt. plan defaults to "Pro". onUpgrade, onNotify, and notifying are optional. The countdown prints hours as h:mm:ss when an hour or more remains, and m:ss otherwise. Seconds are always two digits. Minutes are padded only in the hours form, so the sub-hour display can be "5:09" rather than "05:09". The demo starts at 4,338 seconds, which paints as 1:12:18, and resetsAt is the separate string "3:40 PM". The component does not derive one from the other, and it does not tick. The demo decrements resetsIn once a second. The countdown node is aria-live off, so the digits do not announce every second. The alert role covers the card as a whole. "Upgrade to Max" does not read a target plan from props. A different tier means changing that label. The demo omits onUpgrade, so the button is visible and inert until you pass the handler. The notify button does not schedule a notification. You store notifying, and the pressed style plus "We'll let you know" is the only change the component makes. The two buttons stack, then sit on one row from 400 pixels wide.
Example
import { QuotaInline } from "@/components/ai/quota-banner-inline"
<QuotaInline
remaining={1}
limit={1}
resetsAt="…"
/>Props QuotaInline
| Prop | Type & description | Required |
|---|---|---|
remaining | number | Required |
limit | number | Required |
resetsAt | string | Required |
onUpgrade | () => void | Optional |
Dependencies
- React
- Tailwind CSS
- motion
- lucide-react
Implementation notes
QuotaInline takes remaining, limit, resetsAt, and an optional onUpgrade. It treats remaining at or below zero as out. The ratio is remaining divided by limit, floored at zero, and it drives a ring of radius 6 whose stroke is hidden from assistive tech. The stroke is rose when out, amber when the ratio is under 0.25, and the warm sand tone otherwise. The dash offset eases over 300 milliseconds. At zero the sentence is "No messages left until" the timestamp. Above zero it is the count, "message" or "messages" based on a check for exactly 1, then "left until" the same timestamp. The strip uses a top radius, a bottom border of none, and a single truncating line, so it joins a field underneath. The demo field is a sibling: remaining starts at 3 against a limit of 40, which is already under a quarter, so the ring is amber before anyone types. Each accepted send decrements remaining. At zero the demo disables the input, sets the placeholder to "Upgrade or wait for the reset to keep chatting", and disables send. Its Upgrade handler sets remaining back to 40, which fills the ring and returns it to the sand tone. That refill is the demo, not a plan change inside QuotaInline.
Choosing a layout
Show the warning once, when the percent crosses a line you choose, and let it be dismissed. Eighty percent is only the demo value. Show the reached card when further sends should stop, not as a toast that vanishes on its own. It has no timer for hiding itself. The countdown keeps displaying whatever seconds you pass, and the card stays until you unmount it. The inline strip is for the composer specifically, including while a few messages remain, because it stays in the line of sight above the field without covering the thread. The demo pairs it with a real field so you can watch remaining fall. That field is not part of the export.
Do not make a banner do the job of Usage meter. The ring and the weekly bars are for looking the allowance up. They have no upgrade button and no dismiss. Do not make the banner collect payment either. The reached card can point at a higher plan, but the amount, the new balance, and the pay button live on Credit top-up, for plans that sell credits after the included limit. Model unavailable is a different stop: one model is busy, the copy says nothing was charged, and the way through is a retry or a switch, not a plan reset. If both can happen, they need different sentences, because "out of messages" and "this model is busy" ask the person to do different things.
Which of the three belongs above the composer?
QuotaInline. The source comment calls it a slim notice that sits on top of the composer, and the classes match: rounded top, border with the bottom edge removed, one line of 13 pixel text. QuotaWarning is a card with its own shadow, a progress bar, and a dismiss control. QuotaReached is a taller alert with a clock row and two full-height buttons. Either card will fight the field if you try to glue it on. The inline export also has no dismiss, because it is meant to stay until remaining or resetsAt changes. Upgrade is a text button in the sand tone, not the filled bolt button used on the reached card.
Does the reached countdown know the wall-clock reset?
It only knows resetsIn in seconds, plus the resetsAt string printed in the sentence. The demo happens to use "3:40 PM" alongside a start count of 4,338 seconds, and nothing checks that those agree. When you pass zero, the digits read 0:00 and stay there. The card does not then enable a composer, and it does not flip into QuotaInline's "No messages left" copy. If you show both, you still set remaining to 0 yourself. "Tell me when it resets" will not read the clock either. It only toggles the notifying prop you control.