Welcome to Vanilla Breeze
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
Pricing section patterns for SaaS and product landing pages. Tiered cards, feature comparison tables, and billing toggle options.
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:
data-layout="grid":has() selectorThree-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>
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;}
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>
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;}
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>
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;}
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:
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. |
scope attributes. Ensure toggle has proper labeling.Grid layout element documentation
Stack layout element documentation
CTA section patterns
Feature grid patterns