Invoice History
Past invoices with tabular figures, worded statuses rather than bare coloured dots, and per-invoice download labels.
Current plan, next charge with an actual figure and date, payment method, and a prominent pending-cancellation state.
components/billing-plan-summary.tsxnpx hoverlab add billing-plan-summary
Or over MCP, from your editor's agent — no account needed.
Free to read, copy and install, for personal and non-commercial projects. Shipping it in client work or a paid product needs Pro ($79 once). The source lands in your repo and stops being ours — no attribution, nothing to upgrade.
Start a page with this section — add more, order them, and leave with the page source.
Your plan, your next charge and how it is paid.
Current plan
Team$490/month
Next charge
$490.00
on 1 September 2026
Rendered live in your current theme — this is the same component whose source is below, not a screenshot of it.
/**
* <BillingPlanSummary> — current plan, next charge, payment method.
*
* The three questions a billing page exists to answer, in the order people
* ask them: what am I on, what will I be charged and when, and what card is
* it going to.
*
* The next-charge amount is stated as a figure with a date, never as "your
* plan renews automatically". Vagueness here is what generates the "I did
* not know I would be billed" support ticket.
*
* A pending cancellation is surfaced prominently, because the state where a
* user thinks they have cancelled but has not — or has, and forgets — is
* the most expensive misunderstanding on the page.
*
* Server component.
*/
import * as React from 'react'
import { CreditCard, CalendarClock, TriangleAlert, ArrowRight } from 'lucide-react'
export interface BillingPlanSummaryProps {
planName?: string
planPrice?: string
planInterval?: string
features?: string[]
nextChargeAmount?: string
nextChargeDate?: string
card?: { brand: string; last4: string; expiry: string }
/** Set when the plan is scheduled to end rather than renew. */
cancelsOn?: string
manageHref?: string
className?: string
}
export function BillingPlanSummary({
planName = 'Team',
planPrice = '$490',
planInterval = 'month',
features = ['Up to 10 seats', 'SSO and audit log', 'Priority support', 'Invoice billing'],
nextChargeAmount = '$490.00',
nextChargeDate = '1 September 2026',
card = { brand: 'Visa', last4: '4242', expiry: '09/28' },
cancelsOn,
manageHref = '#',
className = '',
}: BillingPlanSummaryProps) {
return (
<section
className={`overflow-hidden rounded-2xl border border-border/60 bg-card/80 backdrop-blur ${className}`}
>
<div className="border-b border-border/60 px-6 py-4">
<h2 className="font-semibold tracking-tight">Billing</h2>
<p className="mt-0.5 text-sm text-muted-foreground">
Your plan, your next charge and how it is paid.
</p>
</div>
{cancelsOn ? (
<p className="flex items-start gap-2 border-b border-border/60 bg-amber-500/5 px-6 py-3 text-sm text-amber-600 dark:text-amber-400">
<TriangleAlert aria-hidden className="mt-0.5 h-4 w-4 shrink-0" />
<span>
Your plan ends on <span className="font-semibold">{cancelsOn}</span>. You keep
full access until then, and you will not be charged again.
</span>
</p>
) : null}
<div className="grid gap-px bg-border/60 md:grid-cols-3">
{/* Current plan */}
<div className="bg-card p-6">
<p className="break-words text-xs font-bold uppercase tracking-wider text-muted-foreground">
Current plan
</p>
<p className="mt-2 flex items-baseline gap-1.5">
<span className="min-w-0 break-words text-2xl font-extrabold tracking-tight">{planName}</span>
<span className="min-w-0 break-words text-sm text-muted-foreground">
{planPrice}/{planInterval}
</span>
</p>
<ul className="mt-4 space-y-1.5">
{features.map((feature) => (
<li key={feature} className="flex items-start gap-2 text-xs text-muted-foreground">
<span aria-hidden className="mt-1.5 h-1 w-1 shrink-0 rounded-full bg-primary border border-transparent" />
{feature}
</li>
))}
</ul>
</div>
{/* Next charge */}
<div className="bg-card p-6">
<p className="break-words text-xs font-bold uppercase tracking-wider text-muted-foreground">
Next charge
</p>
{cancelsOn ? (
<>
<p className="mt-2 text-2xl font-extrabold tracking-tight">None</p>
<p className="mt-1 text-sm text-muted-foreground">
Cancelled — nothing further will be billed.
</p>
</>
) : (
<>
<p className="mt-2 break-words text-2xl font-extrabold tracking-tight">{nextChargeAmount}</p>
<p className="mt-1 flex items-center gap-1.5 text-sm text-muted-foreground">
<CalendarClock aria-hidden className="h-3.5 w-3.5 shrink-0" />
<span className="min-w-0 break-words">on {nextChargeDate}</span>
</p>
</>
)}
</div>
{/* Payment method */}
<div className="bg-card p-6">
<p className="break-words text-xs font-bold uppercase tracking-wider text-muted-foreground">
Payment method
</p>
<p className="mt-2 flex items-center gap-2">
<span
aria-hidden
className="inline-flex h-8 w-11 shrink-0 items-center justify-center rounded-md border border-border/60 bg-muted"
>
<CreditCard className="h-4 w-4 text-muted-foreground" />
</span>
<span className="min-w-0">
<span className="block break-words text-sm font-medium">
{card.brand} ending {card.last4}
</span>
<span className="block break-words text-xs text-muted-foreground">
Expires {card.expiry}
</span>
</span>
</p>
<a
href={manageHref}
className="mt-4 inline-flex items-center gap-1 text-sm font-semibold transition-all hover:gap-2"
>
Manage billing
<ArrowRight aria-hidden className="h-3.5 w-3.5 rtl:rotate-180" />
</a>
</div>
</div>
</section>
)
}
bg-card, text-muted-foreground) — it inherits your theme instead of overriding it.Drop it at components/billing-plan-summary.tsx and import it where you need the section:
import { BillingPlanSummary } from '@/components/billing-plan-summary'6 of this block’s props are simple enough to drive from here. Change them and the block below re-renders — it is the same component whose source is above, not a mock of it. Everything else it accepts is in the table underneath.
Read out of the component’s own type and signature, so this cannot drift from the source below. Every prop has a default — the component renders standalone before you pass it anything.
| Prop | Type | Default |
|---|---|---|
planName | string | 'Team' |
planPrice | string | '$490' |
planInterval | string | 'month' |
features | string[] | ['Up to 10 seats', 'SSO and audit log', 'Prio… |
nextChargeAmount | string | '$490.00' |
nextChargeDate | string | '1 September 2026' |
card | { brand: string; last4: string; expiry: string } | { brand: 'Visa', last4: '4242', expiry: '09/2… |
cancelsOnSet when the plan is scheduled to end rather than renew. | string | — |
manageHref | string | '#' |
className | string | '' |
The same block rendered once to markup, wrapped as a file your framework compiles. Tailwind classes are framework-agnostic, so the design transfers intact — the behaviour does not.
<!--
Plan & Next Charge Summary — markup from the Hoverlab catalog.
This is the block rendered once to HTML and wrapped as a component
file. It is not a port of the React source: the Tailwind classes carry
the design, which is the part that took the work, and they are the same
in every framework.
This block has no interactive behaviour, so nothing is missing.
-->
<section class="overflow-hidden rounded-2xl border border-border/60 bg-card/80 backdrop-blur ">
<div class="border-b border-border/60 px-6 py-4">
<h2 class="font-semibold tracking-tight">Billing</h2>
<p class="mt-0.5 text-sm text-muted-foreground">Your plan, your next charge and how it is paid.</p>
</div>
<div class="grid gap-px bg-border/60 md:grid-cols-3">
<div class="bg-card p-6">
<p class="break-words text-xs font-bold uppercase tracking-wider text-muted-foreground">Current plan</p>
<p class="mt-2 flex items-baseline gap-1.5">
<span class="min-w-0 break-words text-2xl font-extrabold tracking-tight">Team</span>
<span class="min-w-0 break-words text-sm text-muted-foreground">$490/month</span>
</p>
<ul class="mt-4 space-y-1.5">
<li class="flex items-start gap-2 text-xs text-muted-foreground"><span aria-hidden="true" class="mt-1.5 h-1 w-1 shrink-0 rounded-full bg-primary border border-transparent"></span>Up to 10 seats</li>
<li class="flex items-start gap-2 text-xs text-muted-foreground"><span aria-hidden="true" class="mt-1.5 h-1 w-1 shrink-0 rounded-full bg-primary border border-transparent"></span>SSO and audit log</li>
<li class="flex items-start gap-2 text-xs text-muted-foreground"><span aria-hidden="true" class="mt-1.5 h-1 w-1 shrink-0 rounded-full bg-primary border border-transparent"></span>Priority support</li>
<li class="flex items-start gap-2 text-xs text-muted-foreground"><span aria-hidden="true" class="mt-1.5 h-1 w-1 shrink-0 rounded-full bg-primary border border-transparent"></span>Invoice billing</li>
</ul>
</div>
<div class="bg-card p-6">
<p class="break-words text-xs font-bold uppercase tracking-wider text-muted-foreground">Next charge</p>
<p class="mt-2 break-words text-2xl font-extrabold tracking-tight">$490.00</p>
<p class="mt-1 flex items-center gap-1.5 text-sm text-muted-foreground">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-calendar-clock h-3.5 w-3.5 shrink-0" aria-hidden="true">
<path d="M16 14v2.2l1.6 1"></path>
<path d="M16 2v4"></path>
<path d="M21 7.5V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h3.5"></path>
<path d="M3 10h5"></path>
<path d="M8 2v4"></path>
<circle cx="16" cy="16" r="6"></circle>
</svg>
<span class="min-w-0 break-words">on 1 September 2026</span>
</p>
</div>
<div class="bg-card p-6">
<p class="break-words text-xs font-bold uppercase tracking-wider text-muted-foreground">Payment method</p>
<p class="mt-2 flex items-center gap-2">
<span aria-hidden="true" class="inline-flex h-8 w-11 shrink-0 items-center justify-center rounded-md border border-border/60 bg-muted">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-credit-card h-4 w-4 text-muted-foreground" aria-hidden="true">
<rect width="20" height="14" x="2" y="5" rx="2"></rect>
<line x1="2" x2="22" y1="10" y2="10"></line>
</svg>
</span>
<span class="min-w-0">
<span class="block break-words text-sm font-medium">Visa ending 4242</span>
<span class="block break-words text-xs text-muted-foreground">Expires 09/28</span>
</span>
</p>
<a href="#" class="mt-4 inline-flex items-center gap-1 text-sm font-semibold transition-all hover:gap-2">Manage billing<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-right h-3.5 w-3.5 rtl:rotate-180" aria-hidden="true"><path d="M5 12h14"></path><path d="m12 5 7 7-7 7"></path></svg></a>
</div>
</div>
</section>
What each framework gets across the whole catalog — effects convert properly; this rung is markup.
The component, its props, the design tokens it expects and the command that installs it — as one prompt. Paste it into Claude, Cursor, v0 or ChatGPT and what they build around it will match the rest of the catalog instead of inventing its own system.
Want the whole screen instead of this one section? Open a page and copy it entire.
All of them free to read, copy and install — no account, no locked tiles, no watermarked preview. The whole catalog is open, and so are the API and the CLI.
Browse Billing & UsageCopying the code is free. Putting it in client work or a paid product is what Pro is for — the licence, not the access.
Past invoices with tabular figures, worded statuses rather than bare coloured dots, and per-invoice download labels.
Cards on file with the two failures that cost the subscription: expiry called out before it fails, and the default stated on the row rather than implied by ordering.
Self-serve cancellation with no dark patterns: the exact date access ends, what breaks, one honest alternative, and a button that works.
Past the included allowance, with the end-of-month projection and every number the invoice will use — because a surprise bill is a product defect.
Changing seats with the bill shown first: the part-period charge and the new recurring amount as two numbers, the arithmetic spelled out, and the asymmetry stated where it bites — removing a seat refunds nothing today.
One invoice with the arithmetic shown: proration split into a credit and a charge with their own date ranges, tax as a line with its jurisdiction, and totals in a real tfoot.
Consumable credits split into the buckets they actually live in, rendered in spend order, with a proportional warning that only appears when a meaningful balance is about to expire.