21 Tailwind CSS pricing sections for SaaS plan pages, indie product launches, and B2B sales sites — three-tier layouts and highlighted popular tiers, monthly/yearly and currency toggles, usage sliders, per-seat and pay-what-you-want calculators, feature comparison tables and accordions, plus bento, glass, brutalist, synthwave, and cursor-spotlight treatments. Scoped under .tps-NN for no-collision pasting, prefers-reduced-motion guarded, framework-agnostic.
Building the whole landing page, not just this section?
You just browsed 21 Tailwind pricing sections. Cruip makes complete Tailwind CSS templates for landing pages, SaaS websites and admin dashboards, coded in HTML, React, Next.js and Vue and used by 60K+ developers. Single templates start at $49.
The conversion-tested baseline — three equal tiers (Starter / Team / Business) on a forest ground with one emerald accent — ported to utilities. The whole stylesheet reduces to @theme tokens plus the masked check-mark URL, because minmax(0,1fr), the flex-1 CTA push and the reduced-motion fallback all have utility forms that live on the elements that need them.
The lift pattern: the middle tier scales up, gains a sunset gradient and wears a “Most popular” pill notched over its top edge. In utilities the highlight is not a second component — it is the same <article> class list plus five overrides, which is the clearest way to show a reviewer exactly what “recommended” costs.
Nine features, four plans, one real <table> with <th scope> on both axes. The port's lesson is where utilities are weakest: a table's shared cell styling has no ancestor shortcut, so every <td> carries its own class list — and the recommended column's guide-rail tint becomes a per-cell utility instead of one selector.
A zero-JS billing switch: two hidden radios, a sliding gradient thumb and every price in the section swapping at once. The CSS version needs four :has() blocks written far from the elements; the Tailwind version needs peer-checked/y: for the switch's own chrome and one group-has-[#tps04-y:checked]: pair on each price — and the swap rule now sits on the numbers that swap.
Drag the slider and the estimate, the request count and the recommended tier all move — with aria-valuetext announcing the whole result instead of a bare number. The port is a clean split: every layout and colour decision becomes a utility, and the four vendor pseudo-elements that paint a range track stay in CSS, where Tailwind has no variant and should not.
One product, one price, one card — brass on deep navy, an 88px serif numeral and a testimonial doing the anchoring that a tier ladder normally does. The port's interesting move is the before: variant: the diagonal sheen that was a ::before rule becomes six utilities on the card itself, so the decoration travels with the element it decorates.
Newsletter
Enjoying these demos? Get new ones by email.
A short email when a new collection ships. Unsubscribe any time.
A bento canvas instead of a tier row: the Pro tile occupies a 2×2 quadrant and free / team / stat / quote tiles fan out around it. The port swaps named grid areas for span utilities — a real trade, because col-span-2 row-span-2 lives on the tile while grid-template-areas lived in one place and drew the whole map.
Tiers as three stops on one road: a glowing lime spine, numbered station markers and a highlighted “you are here” pane. Reading it as an <ol> is the whole point — and the port keeps the spine as a before: pseudo-element on the list, because a connector belongs to the list, not to any tier.
Black on cream, mono type, hard 8px shadows and three cards sitting askew on graph paper. The port lands on a genuinely better mechanism than the CSS: because Tailwind compiles rotate-* and translate-* to the individual rotate and translate properties, the hover nudge no longer wipes out each card's tilt — which in the CSS version needs the rotation repeated inside every hover rule.
Frosted cards floating over three slow-drifting aurora blobs. The port keeps the whole glass recipe in the class list — bg-white/6, border-white/12, backdrop-blur-[20px] backdrop-saturate-150 — and moves the one genuinely CSS-only part, the drift keyframes, into an --animate-* theme token so the blobs read as animate-tps10-drift on the element.
Real interactive math: native +/− buttons and a number field drive a live per-seat × seats × cycle total. The port moves every visual state into the class list — the checked cycle label via peer-checked:, the clamped-out buttons via disabled: — leaving the JS to do arithmetic and nothing else.
Three cards wearing a conic-gradient border that rotates forever, driven by an @property-registered angle. The port is honest about its limits: the registration, the mask-composite ring and the keyframes stay in CSS — but the angle's value, the two gradient recipes and every other pixel become utilities, and the reduced-motion fallback moves onto the element where you can see it.
A print-price-list layout: value-prop copy on the left, two compact tier rows stacked on the right, set in Playfair over cream. The port's quiet win is the hover treatment — the recommended row's permanent 4px offset shadow and the other row's hover shadow are now the same utility, declared once as a resting state and once behind hover:.
Three cards standing in a 1400px perspective — the outer two angled back, the centre one square on, any card snapping forward on hover. The port is where v4's per-axis transform utilities earn their keep: rotate-y-*, translate-z-* and translate-y-* compose through separate variables, so the hover state overrides one axis without restating the other two.
Newsletter
Enjoying these demos? Get new ones by email.
A short email when a new collection ships. Unsubscribe any time.
Magenta and cyan neon over a perspective grid horizon, a striped retro sun behind the cards and a chrome-reflection heading. The port's headline is text-shadow-* — a real utility since v4.1 — which turns eight hand-written glow declarations into text-shadow-[0_0_8px_…] on the elements that glow.
Five preset amount chips, the selected one lifting into a mint gradient, and a CTA whose label changes with the choice — all from five radios and no JavaScript. The port replaces eight :has() rules at the bottom of the stylesheet with group-has-[#tps-16-5:checked]: variants sitting on the spans they actually control.
Three currencies, nine prices, zero JavaScript: hidden radios plus group-has-[…]: swap every visible price across all three tiers at once. Burgundy on cream, with the featured tier inverted — and the port's clean-up is the inverted card, whose eight descendant override rules become ordinary utilities on the elements themselves.
A seamless logo marquee above the tiers — social proof first, decision second — on slate with a lime accent. The port's interesting part is not the animation but the containment: the marquee's w-max track will grow the whole section to 2000px unless the wrapper is explicitly bounded, and in utilities that bound is three classes you can actually see.
Three tiers whose full feature lists live inside native <details> elements — short page, deep detail on demand, keyboard-accessible for free. The port's neat bit is v4's open: variant: the chevron rotation and the extra open-state padding are stated on the elements, so the disclosure's whole visual behaviour is one class list.
Amber light follows the cursor across black cards — and lands centred when you tab to one, so keyboard users get the same affordance. The JS sets two custom properties and stops there; group-hover: and group-focus-within: own the reveal, which is the cleanest possible division between script and style.
The anchor-and-discount pattern, stated plainly: the monthly price struck through in orange, the yearly-equivalent price beneath it, and a "Save $84/yr" tab under both. The port's small pleasure is line-through decoration-tps21-orange decoration-2 — the struck price's whole treatment in three utilities, where CSS needs three separate text-decoration-* declarations.
FAQ
Frequently asked questions
How does the monthly/yearly billing toggle in Demo 04 swap every price without JavaScript?
Demo 04 runs the switch entirely in HTML and CSS. Two hidden radio <input> elements share one name; the visible pill is a pair of <label> elements, and the sliding gradient thumb is an absolutely-positioned element the checked radio drives through a sibling selector. Every price is two <span> stacked in the same grid cell — the yearly figure hidden by default. When the yearly radio is :checked, a :has() rule on the section (Tailwind's group-has-[…]:) flips which span shows and moves the thumb. Because the state lives in the radio rather than a script, it works before any hydration and survives a copy-paste into any stack. The one rule that matters: hide the radios with a visually-hidden clip, notdisplay:none, or they drop out of the tab order and keyboard users can no longer flip billing.
How do the currency switcher and the pay-what-you-want card change prices with no script?
Same radio-plus-:has() mechanism, scaled up. Demo 17 holds three currency radios and renders nine price spans; each visible price is a small stack where only the span matching the checked currency is shown, and a group-has-[…]: rule on the container decides which one. Demo 16 uses the pattern for preset amounts — five chip <label> elements over hidden radios, the checked chip lifting into a mint gradient, and the CTA label reacting to the selection through the same has-selector. The trap people hit is putting the state on the clicked element itself: keep the checked radio as the single source of truth and let ancestor :has() rules read it, so one interaction updates the price, the chip, and the button label in one repaint. All prices stay in the DOM, so nothing flashes in on load.
Why does my highlighted 'Most popular' tier overlap or clip its neighbours?
Demo 02 lifts the middle tier with scale and a taller card, and the usual bug is that the transform makes it visually larger while its grid track stays the same width, so the scaled edges bleed over the neighbouring cards. Fix it by giving the popular card real dimensions instead of only a transform — let it own a slightly wider or taller grid cell, add vertical padding, and use z-index so it sits above rather than under the siblings. If you keep the scale, add matching horizontal margin so the enlarged footprint has somewhere to go. The second cause is unequal card heights: put the tier row in a grid or flex container with align-items:stretch so every card matches the tallest, otherwise the lifted card looks like a layout accident rather than a deliberate emphasis. The 'Most popular' pill should be a real badge, not a pseudo-element, so screen readers announce it.
How do I make the feature comparison table in Demo 03 accessible to screen readers?
Demo 03 is a real <table>, which is exactly right — a comparison grid built from <div> elements gives assistive tech no row/column relationship to announce. The load-bearing detail is scope on both axes: the plan names across the top are <th scope="col"> and the feature names down the left edge are <th scope="row">. With both in place, a screen reader reading a checkmark cell announces the feature and the plan it belongs to, so a blind user knows Priority support — Business instead of a bare 'yes'. Give the table a <caption> naming what it compares. Avoid conveying included/excluded by colour alone — a green tick and a grey dash read identically to a colour-blind user, so pair each with a visible glyph or text. On narrow screens let the table scroll inside a wrapper with overflow-x:auto rather than collapsing columns, which usually destroys the header association.
Do the usage slider and per-seat calculator need JavaScript, and how do I keep them accessible?
Yes, these two are the interactive exceptions. Demo 05 is a range <input> whose value drives the estimate, the request count and the recommended tier; because a native range control is already keyboard-operable with arrow keys, the work is in the ARIA — set aria-valuenow, aria-valuemin, aria-valuemax and an aria-label so a screen reader reports the current usage, and wire the live figures through an element with aria-live so updates are spoken. Demo 11 drives per-seat math from native <button> plus/minus controls around a number <input>; keep it a real number field so typing, spinners and mobile numeric keyboards all work, give each button an aria-label like 'add a seat', and clamp the value in the handler so it can't go below one. In both, the script only reads a value and writes text — it never becomes the source of truth for the price the user copies.
How does the rotating conic-gradient border in Demo 12 work, and what breaks in older browsers?
Demo 12 registers a custom property with @property — typed as an <angle> with an initial value — then animates that angle from 0 to 360 degrees and feeds it into a conic-gradient painted behind the card. Registering the property is what makes the angle actually interpolate; a plain custom variable animated the same way jumps rather than spins, which is the number-one reason people's border 'doesn't move'. The gradient lives on a pseudo-element or a slightly larger backing layer with the card content masked on top, so only the rim shows colour. For browsers without @property support the animation simply won't run — so make the static gradient border look finished on its own, and always wrap the spin in @media (prefers-reduced-motion: reduce) to stop it, since a perpetually rotating rim is exactly the kind of motion that triggers discomfort.
How does the cursor-spotlight card in Demo 20 stay usable for keyboard users?
Demo 20 tracks the pointer and moves an amber radial glow to follow it across the black cards, which by itself would be a mouse-only effect — a keyboard user tabbing through the plans would see nothing. The fix baked into this demo is that focusing a card with the keyboard lands the spotlight centred on it, so the same visual affordance that guides a mouse also marks the focused tier. Practically that means driving the glow position from two sources: pointer coordinates on mousemove, and a reset-to-centre on :focus-visible. Keep a real, visible focus ring underneath as well — the glow is decoration, not a focus indicator, and it must not be the only signal that a card is active. The whole effect is enhancement over a card that already reads correctly with no pointer and no script, so nothing about pricing or the CTA depends on the spotlight running.
When should I not use a feature comparison table or a billing toggle on a pricing page?
Skip the Demo 03 comparison table when you have three or fewer plans with short feature lists — a nine-row grid there just adds scanning cost, and the three-card layouts (Demo 01, Demo 02) convert better because the eye compares whole tiers at a glance. Reach for the table only when plans differ across many features and buyers genuinely cross-shop line by line. Skip the Demo 04 billing toggle when you don't actually offer annual billing, or when the yearly discount is trivial — a toggle implies a meaningful saving, and an empty one erodes trust; the Demo 21 struck-through annual-savings stripe states the discount more honestly for a single plan. And don't ship the single premium card (Demo 06) when you have real tiers to show — one card hides the anchoring that makes a middle plan feel reasonable. Match the pattern to the number of plans and the size of the decision, not to which demo looks nicest.