Staaarter

Billing Discount Badge

A promotional discount badge for pricing pages, checkout flows, and upgrade prompts. Shows the computed percent-off next to the discounted price and the struck-through original price, with an optional expiry note and a hover-lift on the call-to-action. Built with shadcn/ui Badge and Button components, Lucide icons, and Tailwind CSS.

By Staaarter Team
Billing
Updated July 5, 2026
Docs
Live preview

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-discount-badge.json

Prefer not to use the CLI? Copy the source from the Code toggle above into src/components/blocks/billing/billing48.tsx instead.

Usage

The file also exports billing48Demo, 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 { Billing48, billing48Demo } from "@/components/blocks/billing/billing48";

export default function Page() {
  return <Billing48 {...billing48Demo} />;
}

Props

PropTypeDefaultDescription
labelstring"Limited-time offer"Small label next to the percent-off badge.
originalPricenumbernoneFull price shown struck through, also used to compute the percent-off value.
discountedPricenumbernoneDiscounted price shown as the primary figure.
currencySymbolstring"$"Symbol shown before both prices.
periodstring"/month"Billing period suffix shown after the discounted price.
expiresLabelstringnoneStatic text describing when the offer ends, omitted if not passed.
ctaLabelstring"Claim discount"Label on the call-to-action button.
ctaHrefstring"#"Destination for the call-to-action link.
classNamestringnoneExtra classes for the outer container.

Behavior notes

  • The percent-off value is computed from originalPrice and discountedPrice on every render, it's never passed in directly, so the badge and the two price figures can never disagree.
  • Both prices are formatted with toFixed(2), so pass numeric dollar values rather than pre-formatted strings.
  • expiresLabel is a plain string, not a live countdown, if you need a real ticking timer you'll need to add your own interval and pass the formatted remaining time in on each render.
  • Hovering the card lifts the call-to-action button slightly and darkens the border, both handled with Tailwind's group-hover utilities rather than JavaScript state.
  • The call-to-action renders as a real next/link through the Button component's render prop, so it behaves like a normal link (right-click, open in new tab) rather than a JavaScript-only click handler.

Frequently asked questions