Billing Annual Discount
A billing promo card that encourages a subscriber to move from monthly to annual billing. It takes the plain monthly and annual prices and works out the discount percent, total savings, and months-free figure itself, then shows a monthly-equivalent price alongside a crossed-out monthly rate and a savings badge. Built for upgrade prompts inside a billing settings page or a pricing page's annual toggle callout.
Docs
Installation
A real shadcn registry command, not a copy-paste stand-in: it fetches this block plus its badge, button dependencies and any missing npm packages, and installs them straight into your project.
npx shadcn add https://staaarter.com/r/billing-annual-discount.jsonPrefer not to use the CLI? Copy the source from the Code toggle above into src/components/blocks/billing/billing19.tsx instead.
Usage
The file also exports billing19Demo, the exact props behind the preview above. Spread it to get a working section in one line, then replace it with your own data.
import { Billing19, billing19Demo } from "@/components/blocks/billing/billing19"; export default function Page() { return <Billing19 {...billing19Demo} />; }
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| heading | string | "Switch to annual billing" | Heading at the top of the card. |
| description | string | none | Supporting line under the heading. |
| planName | string | none | Optional plan name shown next to the billed-annually line. |
| monthlyPrice | number | none | Regular monthly price in dollars. Used to compute the full annual price (times 12) and the savings. |
| annualPrice | number | none | Discounted price for the whole year, in dollars. Divided by 12 for the displayed monthly-equivalent price. |
| features | string[] | none | Optional bullet list of reasons to switch, rendered with check icons. |
| ctaLabel | string | "Switch to annual" | Label on the primary button. |
| ctaHref | string | "#" | Link target for the primary button. |
| secondaryLabel | string | "Stay monthly" | Label on the secondary link, only rendered if secondaryHref is set. |
| secondaryHref | string | none | Link target for the secondary action. Omit to hide the secondary button entirely. |
| className | string | none | Extra classes for the outer container. |
Behavior notes
- All pricing math happens inside the component from the two numeric props: full annual price is monthlyPrice times 12, savings is that figure minus annualPrice, and the discount badge percent and months-free estimate are derived from those, so you never pass a pre-computed savings string.
- The savings badge only renders when the computed discount percent is greater than zero, so passing an annualPrice equal to or above monthlyPrice times 12 just hides the badge instead of showing a negative or zero discount.
- The monthly price is shown struck through next to the new monthly-equivalent figure so the comparison reads at a glance without needing a separate toggle.
- The secondary 'stay monthly' link only renders when secondaryHref is provided, so a single-CTA layout is just a matter of omitting that prop.
Frequently asked questions
More Billing Blocks
View all →Billing Invoice List
Invoice history table with status badges, formatted amounts, and a per-row download link, built with shadcn/ui and Tailwind CSS.
Billing Failed Payment Alert
High-contrast failed-payment alert with a message, a retry button, and an optional update-payment-method button, built with shadcn/ui and Tailwind CSS.
Billing Credit Balance
Compact credit balance card with a large total, an optional purchased-versus-promotional breakdown, a low-balance notice, and a top-up button.
Billing Payment Methods
A saved payment method list for React and Next.js with brand icons, a default badge, near-expiry warnings, and remove/set-default actions per row.
Billing Add Card
A card entry form for React and Next.js with auto-formatted fields, internal brand detection, and per-field validation feedback built with shadcn/ui and Tailwind CSS.
Billing Coupon Code
An inline promo code input for React and Next.js with a simulated loading state, an applied summary showing the discount amount, and an invalid-code message.