# Beste UI

> Beautiful, accessible blocks, pieces and components for shadcn/ui and Tailwind CSS, built for React and Next.js. Install one with the shadcn CLI and the code is yours to edit: no runtime dependency, nothing to upgrade.

Markdown rendition of https://ui.beste.co/blog/68-shadcn-pricing-blocks. Every page on the site has one: add `.md` to any address, or send `Accept: text/markdown`.

Last updated: 2026-08-09

Whole catalog for LLMs: https://ui.beste.co/llms.txt. Registry index: https://ui.beste.co/r/registry.json

---
[All posts](/blog)

August 9, 2026·27 min read

# 68 shadcn Pricing Blocks: Tier Grids, Calculators, Comparison Tables, and the Psychology Underneath Them

The pricing category is sixty-eight production-ready blocks built on shadcn/ui: tier grids with billing toggles, per-seat and usage calculators, comparison tables, credit packs, service ranges, and enterprise panels. Plus what the research actually says about anchoring, decoys and choice overload, the exact point where pricing UX turns deceptive, and the law that now draws that line.

pricingshadcnreactnextjsux

![zieg](/_next/image?url=%2Fassets%2Fimages%2Fzieg.jpg&w=96&q=75&dpl=dpl_CY2dAqud5esqh3XyqpW5XSreDKej)

ziegbuilding beste.co

[zieg's website](https://zieg.beste.co)[zieg on X](https://x.com/forwardset)[zieg on LinkedIn](https://linkedin.com/in/ziegfiroyt)

Every other section of a marketing site describes the product. The pricing page briefly is the product. It is the one screen where a layout decision moves money in both directions: a clear table closes a deal the rest of the site opened, and a clever table closes one that comes back as a refund, a chargeback, or a one-star review about the annual charge nobody saw coming.

That is why pricing is the section people rewrite the most and finish the least. The design problem looks like a card grid. The real problem is that you are asking a stranger to translate their situation into your unit of value, in about ninety seconds, using words you invented.

The pricing category on Beste UI is now sixty-eight blocks. This post is the map: the shapes a price can take on screen and which block implements each one, then the part nobody puts in a component library, which is what the research says about how people read prices, where that knowledge turns into manipulation, and the surprisingly precise legal line that now sits underneath it.

### A pricing page is a model of your business, rendered

Before any of this is a design question it is an arithmetic question: what is the unit you charge for, and does it grow with the value the customer gets?

Seats, requests, sites, seconds of video, projects, revenue processed. Ramanujam and Tacke call this the value metric in _Monetizing Innovation_, and their argument is the one worth stealing: the pricing conversation belongs at the start of the product, not at the end. A page cannot rescue a unit that does not track value. If you charge per seat for a tool where the value comes from one power user and forty read-only viewers, no card grid saves you. Your customers will simply share a login, which is a design review you receive as a support ticket.

Once the unit is right, the page has three jobs, in this order:

1. **Tell people what it costs.** Sounds trivial. A great many B2B pricing pages fail it on purpose.
2. **Help a reader find themselves.** Nobody reads a pricing table left to right. They scan for the row that describes their situation and then read only that column.
3. **Not lie.** Including the passive lies: the price that is monthly only if you pay for the year, the seat count that is a minimum, the fee that appears at checkout.

Every block below is an implementation of jobs one and two. Job three is on you, and the last third of this post is about how to keep it.

### What installs

Each block is a single React component. The CLI resolves the whole tree in one command:

bashCopy

```
npx shadcn add "https://ui.beste.co/r/pricing13?email=YOUR_EMAIL&license_key=YOUR_KEY"
```

That lands the section at `components/beste/block/pricing13.tsx`, plus whatever shadcn/ui primitives it composes (`button`, `badge`, `slider`, `tabs`, `accordion`, `tooltip`), installed only if you do not already have them. No runtime dependency on us, no theme provider, no license check inside your bundle. It is TSX in your repo, which is the [whole point of the copy-paste model](/blog/copy-paste-vs-library).

If you are on Base UI rather than Radix, swap `/r/` for `/r-base/` and you get [the Base UI build](/blog/base-ui-the-new-default) of the same tree. Colors, radii and the emphasis on the recommended tier all resolve through your CSS variables, so the blocks arrive wearing your brand rather than ours. [The anatomy of a shadcn theme](/blog/anatomy-of-a-shadcn-theme) covers those tokens if you are still shaping them.

### The shapes a price takes

Sixty-eight blocks is not sixty-eight ideas. It is six shapes, each with variations for the design language and the amount of detail you owe the reader. Pick the shape by looking at your own billing model, not at which screenshot is prettiest.

#### One: tier grids, and the problem of the middle column

The three-card grid is the default for a reason. It is compact, it scans in a diagonal, and it maps cleanly onto small, medium and large.

Three Tiers With Billing Toggle

[View block](/block/pricing71)

Loading preview…

pricing71: three tiers where the monthly and yearly toggle swaps both the figure and the billing note on every card, so the small print can never disagree with the big number.

Two details in that block are worth copying regardless of which one you install. The toggle changes the period label at the same time it changes the price, which sounds obvious until you audit real pricing pages and find the yearly figure sitting above the word "month" with the explanation somewhere near the footer. And the recommended tier is marked with an outline in the accent color rather than by being physically taller, so the three cards keep one baseline at every width, which is the difference between a grid that survives [a narrow container](/blog/container-queries-for-blocks) and one that stacks into a random order.

The middle column is also where the whole page can quietly turn into a trick, and the research section below is about exactly that. For now, one rule: mark a tier as recommended because it is the one most customers should buy, and be able to say why in a sentence.

If your plans differ by commitment rather than by feature set, [pricing23](/block/pricing23) is the honest version of annual billing: it shows the yearly figure, the per-month equivalent, and the savings in the same card, so nobody has to divide by twelve to check your claim.

| Block                         | Shape                                      | Use it when                                      |
| ----------------------------- | ------------------------------------------ | ------------------------------------------------ |
| [pricing60](/block/pricing60) | Three tiers, emphasized middle             | The default, when plans differ by size           |
| [pricing21](/block/pricing21) | Minimal three-tier                         | Early product, short feature lists               |
| [pricing6](/block/pricing6)   | Three tiers with monthly and yearly toggle | Two billing cycles, one grid                     |
| [pricing61](/block/pricing61) | Toggle variant with feature lists          | Same, with longer inclusion lists                |
| [pricing71](/block/pricing71) | Toggle that swaps figure and billing note  | When the small print must move with the price    |
| [pricing7](/block/pricing7)   | Two tiers, clear upgrade path              | Freemium with one paid plan                      |
| [pricing10](/block/pricing10) | Four horizontal cards                      | Four plans that still need side-by-side scanning |
| [pricing23](/block/pricing23) | Annual price with per-month equivalent     | Annual-first pricing, shown honestly             |
| [pricing28](/block/pricing28) | Tabbed monthly, quarterly, annual          | Three or more commitment options                 |
| [pricing40](/block/pricing40) | Compact button-toggle selector             | Few options, tight vertical space                |
| [pricing18](/block/pricing18) | Individual and Team tabs                   | One product, two audiences, different features   |
| [pricing24](/block/pricing24) | Tiers by funding stage                     | Startup programs, bootstrapped to Series B       |
| [pricing34](/block/pricing34) | Radio-card selection                       | Checkout and in-app plan switching               |
| [pricing51](/block/pricing51) | Icon-highlighted key features              | When one benefit per plan carries the decision   |
| [pricing59](/block/pricing59) | Tabbed membership with summary panel       | Memberships and clubs                            |
| [pricing63](/block/pricing63) | Two contrasting full panels                | Two engagement models, not two sizes             |
| [pricing65](/block/pricing65) | Monochrome editorial tiers                 | Studios and brand-led sites                      |
| [pricing69](/block/pricing69) | Boxless ruled columns                      | Editorial layouts where cards feel loud          |

#### Two: one plan, and one plan plus parts

A single plan is the strongest pricing page you can ship, and the hardest to earn. It says the product does one thing and you know who it is for.

Single Plan With Full Inclusion List

[View block](/block/pricing72)

Loading preview…

pricing72: one plan, an oversized figure, two actions, and everything included listed as three hairline groups underneath. No comparison, because there is nothing to compare.

The interesting variants are the ones that keep a single base price and let the reader build the rest. [pricing31](/block/pricing31), [pricing32](/block/pricing32), [pricing39](/block/pricing39) and [pricing41](/block/pricing41) are the same mechanic in four skins: a base figure, a set of modules, a total that recomputes as you toggle. They work when your add-ons are genuinely optional. They fail, loudly, when the add-ons are the product and the base price is a decoy for a number nobody actually pays.

The rule we would put on a wall: **an add-on toggle must start switched off.** A prechecked upsell is not a configurator, it is a bill you wrote for someone else, and the section on deception below explains exactly which line it crosses.

| Block                         | Shape                                     | Use it when                                  |
| ----------------------------- | ----------------------------------------- | -------------------------------------------- |
| [pricing16](/block/pricing16) | Minimal single plan                       | One product, one price                       |
| [pricing72](/block/pricing72) | Single plan with full inclusion list      | Long feature list, no tiers                  |
| [pricing66](/block/pricing66) | Single retainer card                      | Studios and ongoing engagements              |
| [pricing42](/block/pricing42) | Features grouped into categories          | Twenty or more inclusions                    |
| [pricing17](/block/pricing17) | Plan plus FAQ                             | Objections that arrive before the click      |
| [pricing29](/block/pricing29) | Plan plus FAQ accordion                   | Same, with more questions                    |
| [pricing9](/block/pricing9)   | Plan with selectable add-ons              | Simple modular upsells                       |
| [pricing64](/block/pricing64) | Spotlight plan beside add-on list         | One clear plan, several modules              |
| [pricing37](/block/pricing37) | Toggle-group add-ons                      | A la carte selection                         |
| [pricing41](/block/pricing41) | Switch-toggled add-ons                    | Feature upgrades on a base plan              |
| [pricing31](/block/pricing31) | Base plan plus dynamic total              | Modular products                             |
| [pricing32](/block/pricing32) | Modular builder with live total           | Enterprise packages                          |
| [pricing39](/block/pricing39) | Checkbox feature builder                  | Customizable service packages                |
| [pricing55](/block/pricing55) | Multi-step plan builder                   | Configurations too long for one screen       |
| [pricing56](/block/pricing56) | Sticky summary sidebar                    | Long pages where the total must stay visible |
| [pricing58](/block/pricing58) | Scrollable features beside a slogan panel | Image-led brand pages                        |

#### Three: the comparison table

Once you have more than about six differences per plan, cards stop working and a table starts. This is the shape people get wrong most often, because a comparison table is a real data table with real semantics, not a grid of divs that happens to look aligned.

Feature Comparison Table

[View block](/block/pricing8)

Loading preview…

pricing8: plans as columns, features grouped by category, and cells that carry either a value or a state. The emphasized column keeps its tint through every row so your eye does not lose the plan it is following.

Three things make the difference between a table people read and a table people skip:

* **Group the rows.** Storage, collaboration, support. Ungrouped feature lists read as a wall, and the reader gives up on exactly the rows that would have sold the upgrade.
* **Prefer values to checkmarks.** "100 GB" beats a tick. A tick tells me the feature exists, a value tells me whether the plan fits.
* **Explain the vocabulary in place.** If a row says "SSO enforcement" or "burst rate", the reader who needs it knows what it is and the reader who does not is now afraid to buy. [pricing33](/block/pricing33) puts a tooltip on each feature and [pricing43](/block/pricing43) uses hover cards for longer explanations. Both need a keyboard-reachable trigger, which is why they are built on the shadcn primitives rather than a `title` attribute.

| Block                         | Shape                                  | Use it when                           |
| ----------------------------- | -------------------------------------- | ------------------------------------- |
| [pricing8](/block/pricing8)   | Grouped multi-plan matrix              | Complex products, three or more tiers |
| [pricing62](/block/pricing62) | Plans as columns, check and dash cells | Straight feature parity comparison    |
| [pricing67](/block/pricing67) | Monochrome ruled matrix                | Editorial and brand-led sites         |
| [pricing15](/block/pricing15) | Free versus Pro, two columns           | Freemium upgrade page                 |
| [pricing44](/block/pricing44) | Collapsible feature groups             | Many features, small viewport         |
| [pricing49](/block/pricing49) | Tabs per plan, features by category    | Mobile-first comparison               |
| [pricing33](/block/pricing33) | Tiers with per-feature tooltips        | Jargon that needs a definition        |
| [pricing43](/block/pricing43) | Hover cards with expanded detail       | Dense information, uncluttered layout |

#### Four: calculators, for when the price is a function

Usage-based and per-seat pricing changes the job of the page. The number is no longer something you display, it is something the reader computes about themselves, and your only real task is to make that computation fast and non-scary.

Per-Seat Calculator

[View block](/block/pricing13)

Loading preview…

pricing13: seats on a slider, the per-seat rate, the discount band it lands in, and the monthly total, all in one frame. The bands on the right light up as you cross them, so the next discount is visible before you need it.

The arithmetic in that block is deliberately readable: it picks the highest volume band whose `minSeats` you have crossed, applies the percentage, multiplies by seats. Because the whole thing is props, your real rate card lives in one object:

tsxCopy

```
import { Pricing13 } from "@/components/beste/block/pricing13";

export default function TeamPricing() {
  return (
    <Pricing13
      pricePerSeat={14}
      volumeDiscounts={[
        { minSeats: 10, discount: 10 },
        { minSeats: 25, discount: 15 },
        { minSeats: 50, discount: 20 },
      ]}
      features={["Everything in Pro", "SAML SSO", "Audit log", "Priority support"]}
    />
  );
}
```

[pricing11](/block/pricing11) does the same for metered products: the slider moves across usage breakpoints rather than a continuous range, which is the honest shape when your rate card actually has steps in it. A continuous slider over a stepped price is a small lie that becomes an angry invoice.

Two rules for any calculator you ship. Call the output an estimate and mean it, including whatever your billing system does about proration and overage. And show the ceiling: [pricing45](/block/pricing45) and [pricing57](/block/pricing57) both display the full band table beside the calculator, so an enterprise reader can see where the curve is going without dragging to the end.

| Block                         | Shape                                 | Use it when                                    |
| ----------------------------- | ------------------------------------- | ---------------------------------------------- |
| [pricing13](/block/pricing13) | Per-seat slider with volume discounts | Team tools priced per user                     |
| [pricing68](/block/pricing68) | Seat slider with cost breakdown panel | Same, with an itemized summary                 |
| [pricing11](/block/pricing11) | Usage slider across tier breakpoints  | APIs and consumption pricing                   |
| [pricing27](/block/pricing27) | Tiered rate per request               | Developer infrastructure                       |
| [pricing57](/block/pricing57) | Volume discount table                 | Rate card shown as a table, not a toy          |
| [pricing45](/block/pricing45) | Enterprise volume calculator          | Large deployments, negotiated bands            |
| [pricing73](/block/pricing73) | Per-site bands with live group total  | Multi-location businesses                      |
| [pricing30](/block/pricing30) | Budget slider that matches a plan     | The reader knows their budget, not their needs |
| [pricing26](/block/pricing26) | ROI calculator                        | Enterprise sales where value needs a number    |

An ROI calculator is a promise

[pricing26](/block/pricing26) is the block to be most careful with. A projection that assumes your best customer's results, presented as the reader's results, is a sales claim with a spreadsheet costume. Seed it with conservative inputs, label the assumptions, and let the reader change them.

#### Five: when it is not a subscription

Not everything is SaaS, and forcing a studio, an agency or a one-time product into a three-tier grid is how a pricing page starts sounding like someone else's business.

Project Estimate Ledger

[View block](/block/pricing70)

Loading preview…

pricing70: project work priced as ranges. Each row is a kind of project, what it usually includes, how long it takes, and where the number starts. The note under the table names the two things that move the price more than anything the studio does.

That block is a small argument in favor of published ranges. Agencies avoid them because every project is different, which is true and also exactly why the reader needs a starting point. A range plus the conditions that move it filters out the wrong enquiries before either side spends a call on them.

Credits ([pricing19](/block/pricing19)) deserve a note of their own, because prepaid balances change how spending feels. Prelec and Loewenstein's work on [the pain of paying](https://pubsonline.informs.org/doi/10.1287/mksc.17.1.4) is the reason: decoupling payment from consumption makes the second act painless. That is genuinely good design for an AI product where every action costs fractions of a cent and a per-request invoice would be unreadable. It becomes a trap the moment credits expire quietly or the balance is hard to find. If you sell credits, show the cost per action, as that block does, and never let expiry be the business model.

| Block                         | Shape                               | Use it when                                     |
| ----------------------------- | ----------------------------------- | ----------------------------------------------- |
| [pricing70](/block/pricing70) | Project estimate ledger with ranges | Studios and agencies, published starting points |
| [pricing25](/block/pricing25) | Fixed-price service packages        | Productized services with timelines             |
| [pricing36](/block/pricing36) | Service tiers with delivery windows | Consulting and professional services            |
| [pricing19](/block/pricing19) | Credit packs with usage examples    | AI tools and pay-as-you-go                      |
| [pricing14](/block/pricing14) | Lifetime deal with original price   | One-time purchase, genuine launch offer         |
| [pricing22](/block/pricing22) | Pay what you want                   | Indie tools, open source, creator products      |
| [pricing53](/block/pricing53) | Bundle pricing with savings         | Product suites                                  |
| [pricing12](/block/pricing12) | Enterprise panel with contact sales | Deals that are actually negotiated              |

On that last one: "Contact sales" is legitimate when the price genuinely depends on a conversation, and a tax on your reader when it does not. If you use [pricing12](/block/pricing12), give it a floor. "Enterprise plans start at $2,000 per month" costs you nothing and saves both sides a scheduling link.

#### Six: the commerce layer around the price

The remaining blocks are not layouts, they are the things a pricing page grows once real money runs through it: currency selection, guarantees, payment methods, social proof, loading states, promo codes.

Regional Currency Pricing

[View block](/block/pricing35)

Loading preview…

pricing35: a region selector that changes the currency and the price together. Regional pricing is a product decision before it is a component, but when you make it, the page has to say which market it is quoting.

Two of these deserve caution rather than enthusiasm.

A promo code field is expensive. Baymard's [checkout usability research](https://baymard.com/research/checkout-usability) has documented for years what an empty coupon box does to a committed buyer: it introduces the possibility that everyone else is paying less, and sends people out of your flow to search for a code they may never come back with. If you run coupons, use [pricing52](/block/pricing52) collapsed behind a link, or apply the discount automatically from the campaign link and show it as an applied line rather than an empty invitation.

And [pricing47](/block/pricing47), the countdown, is the block most likely to be misused, which is the subject of the next two sections.

| Block                         | Shape                            | Use it when                               |
| ----------------------------- | -------------------------------- | ----------------------------------------- |
| [pricing35](/block/pricing35) | Region and currency selector     | Genuine regional pricing                  |
| [pricing50](/block/pricing50) | Accepted payment methods         | Checkout-adjacent pages                   |
| [pricing48](/block/pricing48) | Guarantee and refund badges      | A refund policy you actually honor        |
| [pricing20](/block/pricing20) | Plan with embedded testimonial   | Social proof at the decision point        |
| [pricing38](/block/pricing38) | Ratings and review counts        | Marketplaces and consumer products        |
| [pricing52](/block/pricing52) | Promo code input                 | Campaigns and affiliate programs          |
| [pricing47](/block/pricing47) | Countdown on a promotional price | A deadline that is real                   |
| [pricing46](/block/pricing46) | Skeleton loading state           | Prices fetched from your billing provider |
| [pricing54](/block/pricing54) | Plan recommendation quiz         | Plans too different to compare in a row   |

[pricing54](/block/pricing54) is the interesting one in that list. When your plans differ by kind rather than by size, a short set of questions beats a wide table, and the result explains why it picked what it picked. It is the same idea we built [the questionnaire blocks](/blog/16-shadcn-questionnaire-blocks) on: ask one question, then use the answer.

### What the research says about how people read prices

Pricing psychology has a bad reputation because it is usually delivered as a list of tricks. The findings themselves are older, duller and more useful than the listicles suggest. They describe how people read numbers. They are not permission to manufacture the reading.

**Prices are read relative to something.** Tversky and Kahneman's [work on anchoring](https://www.science.org/doi/10.1126/science.185.4157.1124) is the base finding: an arbitrary first number pulls every judgement that follows. On a pricing page the anchor is whatever the eye lands on first, which is usually your highest tier or your crossed-out original. This is unavoidable, not optional. You are choosing an anchor whether or not you think about it, so choose one you can defend when a customer asks.

**A third option changes how the other two look.** Huber, Payne and Puto's [asymmetric dominance effect](https://academic.oup.com/jcr/article-abstract/9/1/90/1826566) showed that adding an option nobody picks changes which of the remaining options people do pick. The famous illustration is the Economist subscription bundle popularized by Dan Ariely, where a print-only offer that almost nobody chose existed to make the print-plus-web bundle look obvious. The line here is thin but real. A tier that exists because some customers genuinely buy it is a plan. A tier engineered to be visibly worse so the neighbouring column looks generous is a prop, and props are the thing readers eventually notice.

**The left digit does most of the work.** Thomas and Morwitz's [left-digit effect](https://academic.oup.com/jcr/article/32/1/54/1796747) is why $29 and $30 feel further apart than one dollar. Nothing dishonest about it, and nothing magical either. It matters most at the boundary and is noise everywhere else. Spend the meeting on the value metric instead.

**Framing a saving as a loss changes behavior more than framing it as a gain.** That is [prospect theory](https://www.jstor.org/stable/1914185) in one line, and it is why "save $58" outperforms "pay $290". Fine, as long as the $58 is real. If your annual price is simply your monthly price times twelve, do not put a savings badge on the toggle.

**More options are not obviously better, and not obviously worse.** Iyengar and Lepper's [jam study](https://business.columbia.edu/faculty/research/when-choice-demotivating-can-one-desire-too-much-good-thing) is the one everyone cites for choice overload. It is worth knowing that the [meta-analysis by Scheibehenne, Greifeneder and Todd](https://academic.oup.com/jcr/article/37/3/409/1795323) found the aggregate effect close to zero and highly dependent on context. So the honest version of the advice is not "three plans is the magic number". It is that choice hurts when the reader cannot tell the options apart. Four clearly differentiated plans beat three plans separated by adjectives. If you cannot write one sentence explaining who each plan is for, that is your signal, not the count.

The useful summary: these effects describe the room your page is read in. Designing with them means making your real offer legible. Designing against them means engineering a misreading.

### Where it turns into deception

Harry Brignull named these patterns in 2010 and still catalogues them at [deceptive.design](https://www.deceptive.design/). The measurement came later: Mathur and colleagues crawled 11,000 shopping sites for [Dark Patterns at Scale](https://arxiv.org/abs/1907.07032) and found 1,818 instances across roughly 11 percent of sites, sorted into a taxonomy of urgency, scarcity, misdirection, sneaking, obstruction, forced action and social proof. Their method is the part designers should remember: they revisited pages every four hours for five days, which is how you distinguish a deadline from a decoration. A countdown that resets when you come back is not a deadline.

Here is what those categories look like specifically on a pricing page, and what to do instead.

| The pattern       | How it shows up in pricing                                                  | The honest version                                                                   |
| ----------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| Fake urgency      | A countdown that restarts on every visit or every session                   | A deadline stored server-side, the same for everyone, that expires and stays expired |
| Fake scarcity     | "Only 3 seats left at this price" with no inventory behind it               | Say nothing, or state a real cap and honor it                                        |
| Reference pricing | A crossed-out price the product never sold at                               | Cross out a price you actually charged, recently                                     |
| Sneaking          | Add-ons prechecked, plan preselected as the expensive one                   | Everything off by default, the reader adds what they want                            |
| Misdirection      | Monthly figure shown while the annual toggle is silently on                 | The period label changes with the number, every time                                 |
| Hidden costs      | Setup fees, per-transaction fees, mandatory onboarding revealed at checkout | Total cost of the first year visible on the pricing page                             |
| Confirmshaming    | "No thanks, I like paying too much"                                         | A neutral decline                                                                    |
| Obstruction       | Sign up in two clicks, cancel in nine and a phone call                      | Cancel where you subscribed, in the same number of steps                             |
| Forced continuity | A trial that converts with no notice                                        | A reminder before the first charge, sent by you                                      |

Two of those deserve implementation detail, because they are exactly where our own blocks could be misused.

**The countdown.** [pricing47](/block/pricing47) takes an `endTime` prop and counts down to it in an effect. Its demo default is computed from `Date.now()`, which produces a deadline three days from whenever the page happens to render. That is precisely the evergreen countdown you must not ship, and it is fine in a demo for the same reason a lorem ipsum paragraph is fine: nobody is being sold anything. When you wire it up, `endTime` should be a fixed timestamp that came from your campaign configuration, identical for every visitor, and the section should render an ended state rather than quietly restarting. If a deadline is worth putting on the page, it is worth letting it pass.

**The crossed-out price.** [pricing14](/block/pricing14) and [pricing47](/block/pricing47) both display an original price beside a discounted one. That comparison is a factual claim about your own history. Make it one.

There is also a business argument, and it is stronger than the ethical one is usually allowed to be. Deceptive pricing does not fail at the moment of purchase, it succeeds there. It fails four weeks later in refund requests, chargeback ratios, involuntary churn and the specific kind of review that mentions your billing rather than your product. You are borrowing conversion from your future retention at a bad rate.

### The line the law now draws

This stopped being a matter of taste. If you sell into the EU or the US, several of the patterns above are already illegal, and the drafting is unusually specific.

* **False urgency is blacklisted outright in the EU.** Annex I of the [Unfair Commercial Practices Directive](https://eur-lex.europa.eu/legal-content/en/ALL/?uri=CELEX%3A32005L0029) lists practices unfair in all circumstances, with no balancing test. Point 7 covers falsely stating that a product will be available only for a very limited time in order to force an immediate decision. An evergreen countdown is not a grey area, it is on the list.
* **Discount claims have a defined reference price.** [Article 6a of the Price Indication Directive](https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A31998L0006), inserted by the [Omnibus Directive](https://eur-lex.europa.eu/eli/dir/2019/2161/oj), requires an announced price reduction to state the prior price, defined as the lowest price applied in the preceding 30 days. It is written for goods rather than services, so a SaaS subscription is not directly in scope, but it tells you what regulators consider a fake discount, and the UCPD's general prohibition on misleading actions reaches the rest.
* **Interface manipulation is named in the DSA.** Article 25 of the [Digital Services Act](https://eur-lex.europa.eu/eli/reg/2022/2065/oj) prohibits online platforms from designing interfaces that deceive or manipulate users, or that otherwise materially distort their ability to make free and informed decisions.
* **In the US, the rule moved but the exposure did not.** The FTC's click-to-cancel Negative Option Rule was [vacated by the Eighth Circuit in July 2025](https://www.cooley.com/news/insight/2025/2025-07-11-click-to-cancel-just-got-cancelled-eighth-circuit-vacates-entirety-of-ftcs-negative-option-rule) on procedural grounds, days before it took effect. That vacatur removed a rule, not the underlying law: [ROSCA](https://www.ftc.gov/legal-library/browse/statutes/restore-online-shoppers-confidence-act) still requires clear disclosure of subscription terms, express informed consent, and a simple cancellation mechanism, Section 5 still reaches deceptive practices, and state automatic renewal laws, California's in particular, are stricter than the federal rule was.

None of this requires a lawyer for a pricing page. It requires that the page say what you will charge, when, and how to stop.

### The technical part

The design questions are the hard ones, but pricing sections have a small set of implementation traps that are worth naming once.

**Keep the client boundary around the calculator, not the page.** A tier grid is static markup and should render on the server. A seat slider needs state. The blocks that compute are marked `"use client"`, and if you compose a section around them, keep the interactive part as a child rather than making the whole route client-rendered. This is the most common accidental deopt in a Next.js marketing site, and [where the use client boundary actually goes](/blog/use-client-boundary) covers the mental model.

**Format money deterministically.** Called with no argument, `toLocaleString()` and `Intl.NumberFormat` resolve against the runtime's own locale and ICU data, so a server in one region and a browser in another can disagree about whether a thousand is written `1,000` or `1.000`. On a client component that also renders on the server, that disagreement is a hydration error on your highest-intent page. Most of the blocks take prices as preformatted strings, which sidesteps it entirely. Where one does format a number for you, pin the locale: [pricing68](/block/pricing68) calls `toLocaleString("en-US")` for exactly this reason, and that is the line to copy into any calculator you extend.

**Prices belong in one place, and it is not the JSX.** Every block takes its figures as props, so keep the rate card in a single module, or read it from your billing provider. If you are on [Stripe prices](https://docs.stripe.com/products-prices/how-products-and-prices-work) or Polar, fetch on the server, cache it, and use [pricing46](/block/pricing46) for the loading state if the fetch is client-side. A price that exists as a literal in a component is a price that will be wrong in one of the three places it appears.

**A comparison table should be a table.** [pricing8](/block/pricing8) uses real `thead`, `th` and `td` elements, with a minimum width and horizontal scroll instead of a collapse that reflows the cells into an order nobody can follow. Real table markup is what lets assistive technology tie a cell back to its plan, and adding `scope="col"` to the plan headers, plus promoting each feature-name cell to a `th` with `scope="row"`, makes that association explicit rather than inferred. If you rebuild the same layout out of divs, you have made a picture of a table.

**Give state cells an accessible name.** This one is on you when you wire your own values in. A `Check` icon and a `Minus` icon are decoration to a screen reader, and a row that reads "Guest access" followed by silence tells the listener nothing. Add an `sr-only` span of "Included" or "Not included" beside the icon. You own the file, so this is a two-line edit rather than a feature request.

**Announce the number that changes.** When a slider recomputes a total, sighted users see it move and everyone else gets nothing. Wrap the total in an [aria-live="polite"](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-live) region, and make sure the slider itself carries a label. For countdowns, do not announce every second: update a polite region once a minute, or provide the deadline as text and let the timer be decorative. The [WCAG timing guidance](https://www.w3.org/WAI/WCAG22/Understanding/timing-adjustable.html) is worth reading before you put a clock on a page.

**Say what is included in the number.** Tax display is a jurisdiction question, not a design one: consumer prices in the EU are quoted including VAT, US prices typically exclude sales tax, and B2B pricing pages usually quote excluding tax with a note. Pick the convention for your market and put it in the block's period label, where it will be read, rather than in a footer, where it will not.

### Choosing one

If you already know your billing model, this is a short decision:

* One product, one price: [pricing72](/block/pricing72) or [pricing16](/block/pricing16).
* Three plans that differ by size: [pricing60](/block/pricing60), or [pricing71](/block/pricing71) if you bill annually as well.
* Priced per seat or per unit: [pricing13](/block/pricing13) or [pricing11](/block/pricing11), with the band table visible.
* More than six differences per plan: [pricing8](/block/pricing8), grouped, with values instead of ticks.
* Project or retainer work: [pricing70](/block/pricing70), with real ranges.
* Plans that differ by kind: [pricing54](/block/pricing54), and let the answers do the sorting.

The whole category sits at </blocks/pricing>, and every block has a live full-screen preview and its full source on its own page. If you drive an agent, the [MCP server](/blog/beste-ui-mcp-server) can search all of them by intent and compose the section for you.

Then the part no component can do for you. Read your finished page as somebody who has never used your product, and answer three questions out loud: what will I be charged, when will I be charged it, and how do I stop. If the page answers all three without you explaining anything, the psychology takes care of itself. If it does not, no amount of layout will fix it, and the countdown will only make it worse.

Ship the pricing page this afternoon

All sixty-eight pricing blocks, and every other Pro block, are included in one license. Install from the CLI, own the source, and change the numbers in one file.

[Get ProGet Pro](/pricing)

## Build it with real blocks

Every section in this post is a component you can copy into your own codebase and own outright.

[Browse blocksBrowse blocks](/blocks)[Read more postsRead more posts](/blog)

## Ship the interface, keep the code.

New blocks, pieces and components every week. Install one with a command and it is yours to edit. No runtime dependency, no upgrade path to fight.

[Browse the libraryBrowse the library](/blocks)[See pricingSee pricing](/pricing)

### Library

* [Blocks](/blocks)
* [Pages](/pages)
* [Pieces](/pieces)
* [Components](/components)
* [Search](/search)

### Learn

* [Docs](/docs)
* [AI & MCP](/docs/mcp)
* [Blog](/blog)
* [Free tools](/tools)
* [What's new?](/changelog)
* [Website Builder](https://beste.co)

### More

* [Pricing](/pricing)
* [Referrals](/referrals)
* [License](/license)
* [GitHub](https://github.com/beste-co/beste-ui)

### Shadcn Blocks

* [Shadcn Hero Blocks](/blocks/hero)
* [Shadcn Feature Blocks](/blocks/feature)
* [Shadcn Pricing Blocks](/blocks/pricing)
* [Shadcn CTA Blocks](/blocks/cta)
* [Shadcn FAQ Blocks](/blocks/faq)
* [Shadcn About Blocks](/blocks/about)
* [Shadcn Stats Blocks](/blocks/stats)
* [Shadcn Footer Blocks](/blocks/footer)
* [Shadcn Navigation Blocks](/blocks/navigation)
* [Shadcn Auth Blocks](/blocks/auth)
* [Shadcn Ecommerce Blocks](/blocks/ecommerce)
* [Shadcn Portfolio Blocks](/blocks/portfolio)
* [Shadcn Showcase Blocks](/blocks/showcase)
* [Shadcn Careers Blocks](/blocks/careers)
* [Shadcn Onboarding Blocks](/blocks/onboarding)
* [Shadcn Coming Soon Blocks](/blocks/coming-soon)
* [Shadcn Post Blocks](/blocks/post)
* [Shadcn Legal Blocks](/blocks/legal)
* [Shadcn Workflow Blocks](/blocks/workflow)
* [Shadcn News Blocks](/blocks/news)

© 2026, [Beste](https://beste.co). All rights reserved.