Pricing
Pricing section patterns for SaaS and product landing pages. Tiered cards, feature comparison tables, and billing toggle options.
Overview
Pricing sections help users understand and compare your offerings. These patterns use data-layout="grid" for responsive card layouts and semantic table markup for feature comparisons, creating accessible and mobile-friendly pricing displays.
Key features:
- Responsive card grids with
data-layout="grid" - Highlighted recommended tier
- Feature comparison tables with sticky headers
- Monthly/yearly billing toggle with
:has()selector - Accessible markup with proper semantics
- No JavaScript required for basic functionality
Pricing Cards
Three-tier pricing cards in a responsive grid. The middle tier is highlighted as "Most Popular" with accent styling and a badge.
<section class="pricing-section" data-layout="center" data-layout-max="wide" data-layout-gap="2xl"> <header data-layout="stack" data-layout-gap="s"> <h2>Simple, transparent pricing</h2> <p class="text-muted">Choose the plan that's right for you</p> </header> <div data-layout="grid" data-layout-min="280px" data-layout-gap="l"> <article class="pricing-card" data-layout="stack" data-layout-gap="l"> <div data-layout="stack" data-layout-gap="xs"> <span class="plan-name">Basic</span> <div> <span class="price">$9</span> <span class="price-period">/month</span> </div> <p class="text-sm text-muted">Perfect for individuals and small projects</p> </div> <ul class="feature-list"> <li><icon-wc name="check" size="sm"></icon-wc> Up to 3 projects</li> <li><icon-wc name="check" size="sm"></icon-wc> 5 GB storage</li> <li><icon-wc name="check" size="sm"></icon-wc> Basic analytics</li> <li><icon-wc name="check" size="sm"></icon-wc> Email support</li> </ul> <a href="#" class="button secondary full-width">Get Started</a> </article> <article class="pricing-card" data-featured data-layout="stack" data-layout-gap="l"> <span class="pricing-badge">Most Popular</span> <div data-layout="stack" data-layout-gap="xs"> <span class="plan-name">Pro</span> <div> <span class="price">$29</span> <span class="price-period">/month</span> </div> <p class="text-sm text-muted">Best for growing teams and businesses</p> </div> <ul class="feature-list"> <li><icon-wc name="check" size="sm"></icon-wc> Unlimited projects</li> <li><icon-wc name="check" size="sm"></icon-wc> 50 GB storage</li> <li><icon-wc name="check" size="sm"></icon-wc> Advanced analytics</li> <li><icon-wc name="check" size="sm"></icon-wc> Priority support</li> <li><icon-wc name="check" size="sm"></icon-wc> Custom integrations</li> <li><icon-wc name="check" size="sm"></icon-wc> Team collaboration</li> </ul> <a href="#" class="button full-width">Get Started</a> </article> <article class="pricing-card" data-layout="stack" data-layout-gap="l"> <div data-layout="stack" data-layout-gap="xs"> <span class="plan-name">Enterprise</span> <div> <span class="price">$99</span> <span class="price-period">/month</span> </div> <p class="text-sm text-muted">For large organizations with custom needs</p> </div> <ul class="feature-list"> <li><icon-wc name="check" size="sm"></icon-wc> Everything in Pro</li> <li><icon-wc name="check" size="sm"></icon-wc> Unlimited storage</li> <li><icon-wc name="check" size="sm"></icon-wc> Custom analytics</li> <li><icon-wc name="check" size="sm"></icon-wc> 24/7 phone support</li> <li><icon-wc name="check" size="sm"></icon-wc> Dedicated account manager</li> <li><icon-wc name="check" size="sm"></icon-wc> SLA guarantee</li> </ul> <a href="#" class="button secondary full-width">Contact Sales</a> </article> </div></section>
Required CSS
Add these styles for pricing card layouts:
.pricing-card { background: var(--color-surface-raised); border: var(--border-width-thin) solid var(--color-border); border-radius: var(--radius-l); padding: var(--size-l); text-align: center;} .pricing-card[data-featured] { border-color: var(--color-interactive); box-shadow: var(--shadow-m); transform: scale(1.05); position: relative;} .pricing-badge { position: absolute; top: 0; left: 50%; transform: translate(-50%, -50%); background: var(--color-interactive); color: var(--color-white); padding: var(--size-2xs) var(--size-s); border-radius: var(--radius-full); font-size: var(--font-size-sm); font-weight: var(--font-weight-semibold);} .pricing-card .plan-name { font-size: var(--font-size-lg); font-weight: var(--font-weight-semibold); color: var(--color-text);} .pricing-card .price { font-size: var(--font-size-3xl); font-weight: var(--font-weight-bold);} .pricing-card .price-period { font-size: var(--font-size-sm); color: var(--color-text-muted);} .feature-list { list-style: none; padding: 0; margin: 0; text-align: left;} .feature-list li { display: flex; align-items: center; gap: var(--size-s); padding: var(--size-xs) 0;} .feature-list icon-wc { color: var(--color-success); flex-shrink: 0;}
Comparison Table
A feature comparison table with plan columns and feature rows. Uses semantic <table> markup with proper scope attributes for accessibility. The header is sticky for easy comparison while scrolling.
<div class="pricing-table-wrapper"> <table class="pricing-table"> <thead> <tr> <th scope="col">Features</th> <th scope="col"> <div class="plan-header"> <span class="plan-name">Basic</span> <span class="plan-price">$9<span class="plan-period">/mo</span></span> </div> </th> <th scope="col" class="plan-featured"> <div class="plan-header"> <span class="plan-name">Pro</span> <span class="plan-price">$29<span class="plan-period">/mo</span></span> </div> </th> <th scope="col"> <div class="plan-header"> <span class="plan-name">Enterprise</span> <span class="plan-price">$99<span class="plan-period">/mo</span></span> </div> </th> </tr> </thead> <tbody> <tr class="category-row"> <td colspan="4">Usage</td> </tr> <tr> <td>Projects</td> <td>3</td> <td>Unlimited</td> <td>Unlimited</td> </tr> <tr> <td>Storage</td> <td>5 GB</td> <td>50 GB</td> <td>Unlimited</td> </tr> <tr class="category-row"> <td colspan="4">Features</td> </tr> <tr> <td>Custom integrations</td> <td><icon-wc name="x" size="sm" class="feature-unavailable"></icon-wc></td> <td><icon-wc name="check" size="sm" class="feature-check"></icon-wc></td> <td><icon-wc name="check" size="sm" class="feature-check"></icon-wc></td> </tr> <tr> <td>API access</td> <td><icon-wc name="x" size="sm" class="feature-unavailable"></icon-wc></td> <td><icon-wc name="check" size="sm" class="feature-check"></icon-wc></td> <td><icon-wc name="check" size="sm" class="feature-check"></icon-wc></td> </tr> </tbody> <tfoot> <tr> <td></td> <td><a href="#" class="button secondary">Get Started</a></td> <td><a href="#" class="button">Get Started</a></td> <td><a href="#" class="button secondary">Contact Sales</a></td> </tr> </tfoot> </table></div>
Required CSS
Add these styles for pricing tables:
.pricing-table-wrapper { overflow-x: auto;} .pricing-table { width: 100%; border-collapse: collapse; background: var(--color-surface-raised); border-radius: var(--radius-l); overflow: hidden;} .pricing-table thead { position: sticky; top: 0; background: var(--color-surface-raised); z-index: 1;} .pricing-table th,.pricing-table td { padding: var(--size-m); border-bottom: var(--border-width-thin) solid var(--color-border);} .pricing-table th { font-weight: var(--font-weight-semibold); text-align: center; vertical-align: bottom;} .pricing-table th:first-child { text-align: left;} .pricing-table td { text-align: center;} .pricing-table td:first-child { text-align: left; color: var(--color-text-muted);} .pricing-table tbody tr:last-child td { border-bottom: none;} .pricing-table tfoot td { border-bottom: none; padding-top: var(--size-l);} .plan-header { display: flex; flex-direction: column; align-items: center; gap: var(--size-xs);} .plan-name { font-size: var(--font-size-lg);} .plan-price { font-size: var(--font-size-2xl); font-weight: var(--font-weight-bold);} .plan-period { font-size: var(--font-size-sm); color: var(--color-text-muted); font-weight: var(--font-weight-normal);} .plan-featured .plan-header { color: var(--color-interactive);} .feature-check { color: var(--color-success);} .feature-unavailable { color: var(--color-text-muted);} .category-row td { background: var(--color-surface); font-weight: var(--font-weight-semibold); color: var(--color-text); text-align: left !important;}
With Billing Toggle
Pricing cards with a monthly/yearly billing toggle. Uses the CSS :has() selector to show different prices based on the toggle state, requiring no JavaScript for core functionality.
<section class="pricing-section" data-layout="center" data-layout-max="wide" data-layout-gap="2xl"> <header data-layout="stack" data-layout-gap="m"> <h2>Simple, transparent pricing</h2> <p class="text-muted">Choose the plan that's right for you</p> <div class="pricing-toggle-wrapper"> <div class="pricing-toggle"> <span class="toggle-label active" id="monthly-label">Monthly</span> <label class="toggle-switch"> <input type="checkbox" aria-labelledby="monthly-label yearly-label" /> <span class="toggle-slider"></span> </label> <span class="toggle-label" id="yearly-label">Yearly</span> <span class="savings-badge">Save 20%</span> </div> </div> </header> <div data-layout="grid" data-layout-min="280px" data-layout-gap="l" class="pricing-cards"> <article class="pricing-card" data-layout="stack" data-layout-gap="l"> <div data-layout="stack" data-layout-gap="xs"> <span class="plan-name">Basic</span> <div> <span class="price price-monthly">$9</span> <span class="price price-yearly">$7</span> <span class="price-period">/month</span> </div> </div> <!-- features... --> </article> <!-- more cards... --> </div></section>
Required CSS
Add these styles for the billing toggle. The :has() selector enables price switching without JavaScript:
.pricing-toggle { display: inline-flex; align-items: center; gap: var(--size-m);} .toggle-label { font-weight: var(--font-weight-medium); color: var(--color-text-muted); transition: color 0.2s ease;} .toggle-label.active { color: var(--color-text);} .toggle-switch { position: relative; width: 56px; height: 28px;} .toggle-switch input { opacity: 0; width: 0; height: 0;} .toggle-slider { position: absolute; cursor: pointer; inset: 0; background: var(--color-interactive); border-radius: var(--radius-full); transition: background 0.2s ease;} .toggle-slider::before { content: ""; position: absolute; height: 22px; width: 22px; left: 3px; bottom: 3px; background: var(--color-white); border-radius: var(--radius-full); transition: transform 0.2s ease;} .toggle-switch input:checked + .toggle-slider::before { transform: translateX(28px);} .toggle-switch input:focus-visible + .toggle-slider { outline: 2px solid var(--color-focus); outline-offset: 2px;} .savings-badge { background: var(--color-success-subtle); color: var(--color-success); padding: var(--size-2xs) var(--size-s); border-radius: var(--radius-full); font-size: var(--font-size-sm); font-weight: var(--font-weight-semibold);} /* Base hidden state */.price-monthly,.price-yearly { display: none;} /* Price visibility using :has() */.pricing-toggle-wrapper:has(input:not(:checked)) ~ .pricing-cards .price-monthly { display: inline;} .pricing-toggle-wrapper:has(input:checked) ~ .pricing-cards .price-yearly { display: inline;}
Optional JavaScript Enhancement
The core price-switching is CSS-only via :has(). The demo includes a small script for visual label feedback, toggling the .active class on the Monthly/Yearly labels as the user interacts with the toggle:
Layout Configuration
Common data attributes for pricing sections:
| Element | Attribute | Values | Description |
|---|---|---|---|
data-layout="grid" |
data-layout-min |
CSS length (e.g., 280px) |
Minimum card width before wrapping. |
data-layout="grid" |
data-layout-gap |
xs s m l xl |
Gap between grid items. |
data-layout="center" |
data-layout-max |
narrow measure wide |
Maximum width of pricing section. |
data-layout="stack" |
data-layout-gap |
xs s m l xl 2xl |
Vertical spacing between elements. |
.pricing-card |
data-featured |
Boolean attribute | Highlights the recommended plan. |
Usage Notes
- Tier naming: Use clear, hierarchical names (Basic, Pro, Enterprise) that indicate value progression.
- Feature ordering: List features in order of importance. Lead with the most compelling features.
- Highlight recommended: Always highlight one tier as recommended. This guides user decisions and improves conversion.
- CTAs: Use primary buttons for the recommended plan and secondary buttons for others. Use "Contact Sales" for enterprise tiers.
- Pricing transparency: Show all costs upfront. If there are per-user or usage-based costs, make them clear.
- Yearly savings: When offering discounts for annual billing, display the savings prominently (e.g., "Save 20%").
- Comparison tables: Use tables for detailed feature comparisons. Cards work better for quick overviews.
- Mobile considerations: Cards stack naturally on mobile. Tables may need horizontal scrolling.
- Accessibility: Use proper table semantics with
scopeattributes. Ensure toggle has proper labeling.
Related
Layout Grid
Grid layout element documentation
Layout Stack
Stack layout element documentation
Call to Action
CTA section patterns
Features
Feature grid patterns