Back to Widget Patterns
v2.5Interactive Tooling

Pricing Tier Configurator

The user adjusts inputs (seats, usage, features) and the component highlights one of N pricing tiers as recommended, updating in real time. Composes PrimaryButtonCTA.

Live preview

Rendered with real component code. Each example demonstrates a documented variant.

SaaS pricing

Find your plan

Adjust the inputs below and we will highlight the right tier.

5
Most popular

Pro

For growing teams who need advanced tooling.

$79/mo
  • Up to 30 seats
  • 100 GB storage
  • Priority support
  • Advanced analytics
  • All integrations
Choose Pro

Enterprise

Custom controls and dedicated support at scale.

Custom
  • Unlimited seats
  • Unlimited storage
  • Dedicated success manager
  • SSO and audit logs
  • Custom SLA
Contact sales

Vertical layout

Find your plan

Adjust the inputs below and we will highlight the right tier.

5
Most popular

Pro

For growing teams who need advanced tooling.

$79/mo
  • Up to 30 seats
  • 100 GB storage
  • Priority support
  • Advanced analytics
  • All integrations
Choose Pro

Enterprise

Custom controls and dedicated support at scale.

Custom
  • Unlimited seats
  • Unlimited storage
  • Dedicated success manager
  • SSO and audit logs
  • Custom SLA
Contact sales

React props

Configure the component via props in React or via attributes in HTML.

PropTypeDefaultDescription
titlestringundefinedSection heading rendered above the inputs and tiers
descriptionstringundefinedSupporting copy below the heading
tiersTier[]requiredPricing tier definitions; each tier has slug, name, price, features, and cta
inputsInput[]requiredInputs the user adjusts; supports slider, select, and checkbox types
recommend(values: Record<string, number | string | boolean>) => stringrequiredReturns the slug of the recommended tier based on current input values
layout"horizontal" | "vertical""horizontal"Tier layout direction; horizontal uses auto-fit grid columns, vertical stacks tiers
onTierChange(slug: string) => voidundefinedCalled when the recommended tier slug changes
classNamestringundefinedExtra class applied to the root element

HTML usage

The HTML variant uses the same shared styles and class names. Drop into any stack.

<link rel="stylesheet" href="path/to/pricing-tier-configurator/styles.css">

<section class="ptc ptc--horizontal" id="pricing-configurator">
  <header class="ptc__header">
    <h2 class="ptc__title">Find your plan</h2>
    <p class="ptc__description">Adjust the inputs and we will highlight the right tier.</p>
  </header>

  <div class="ptc__inputs" aria-live="polite">
    <div class="ptc__input-field">
      <label class="ptc__input-label" for="ptc-seats">How many seats do you need?</label>
      <div class="ptc__input-row">
        <input id="ptc-seats" class="ptc__slider" type="range" min="1" max="100" step="1" value="5">
        <span class="ptc__slider-value" id="ptc-seats-value">5</span>
      </div>
    </div>
    <div class="ptc__input-field">
      <div class="ptc__checkbox-row">
        <input id="ptc-analytics" class="ptc__checkbox" type="checkbox">
        <label class="ptc__input-label" for="ptc-analytics">Need advanced analytics?</label>
      </div>
    </div>
    <div class="ptc__input-field">
      <div class="ptc__checkbox-row">
        <input id="ptc-sso" class="ptc__checkbox" type="checkbox">
        <label class="ptc__input-label" for="ptc-sso">Need SSO or audit logs?</label>
      </div>
    </div>
  </div>

  <div class="ptc__tiers" id="ptc-tiers">
    <!-- Tiers rendered by script below -->
  </div>
</section>

<script>
(function () {
  var seats = document.getElementById('ptc-seats');
  var seatsVal = document.getElementById('ptc-seats-value');
  var analytics = document.getElementById('ptc-analytics');
  var sso = document.getElementById('ptc-sso');
  var tiersEl = document.getElementById('ptc-tiers');

  var tiers = [
    { slug: 'starter', name: 'Starter', price: '$19/mo',
      features: ['Up to 5 seats', '10 GB storage', 'Email support'],
      cta: { label: 'Choose Starter', href: '/signup?plan=starter' } },
    { slug: 'pro', name: 'Pro', price: '$79/mo',
      features: ['Up to 30 seats', '100 GB storage', 'Priority support', 'Advanced analytics'],
      cta: { label: 'Choose Pro', href: '/signup?plan=pro' }, badge: 'Most popular' },
    { slug: 'enterprise', name: 'Enterprise', price: 'Custom',
      features: ['Unlimited seats', 'Unlimited storage', 'SSO and audit logs'],
      cta: { label: 'Contact sales', href: '/contact/sales' } },
  ];

  function recommend() {
    var s = Number(seats.value);
    if (sso.checked) return 'enterprise';
    if (s > 30 || analytics.checked) return 'pro';
    return 'starter';
  }

  function render() {
    var rec = recommend();
    seatsVal.textContent = seats.value;
    tiersEl.innerHTML = tiers.map(function (t) {
      var cls = 'ptc__tier' + (t.slug === rec ? ' ptc__tier--recommended' : '');
      return '<div class="' + cls + '">'
        + (t.badge ? '<span class="ptc__badge">' + t.badge + '</span>' : '')
        + '<h3 class="ptc__tier-name">' + t.name + '</h3>'
        + '<div class="ptc__tier-price">' + t.price + '</div>'
        + '<ul class="ptc__tier-features">' + t.features.map(function (f) {
            return '<li>' + f + '</li>';
          }).join('') + '</ul>'
        + '<a class="pbc pbc--' + (t.slug === rec ? 'solid' : 'outlined') + ' pbc--rounded"'
        + ' href="' + t.cta.href + '">' + t.cta.label + '</a>'
        + '</div>';
    }).join('');
  }

  seats.addEventListener('input', render);
  analytics.addEventListener('change', render);
  sso.addEventListener('change', render);
  render();
}());
</script>

Customization

Override these CSS custom properties to integrate the component into your brand.

.ptc {
  --ptc-bg: var(--brand-surface, white);
  --ptc-text-color: var(--brand-ink, #102542);
  --ptc-muted-color: rgba(16, 37, 66, 0.6);
  --ptc-accent: var(--brand-accent, #1e5fcf);
  --ptc-tier-bg: white;
  --ptc-tier-border: rgba(0, 0, 0, 0.1);
  --ptc-tier-recommended-bg: rgba(30, 95, 207, 0.04);
  --ptc-tier-recommended-border: var(--ptc-accent);
  --ptc-tier-recommended-shadow: 0 8px 32px -8px rgba(30, 95, 207, 0.25);
  --ptc-input-bg: rgba(0, 0, 0, 0.025);
  --ptc-input-border: rgba(0, 0, 0, 0.12);
  --ptc-badge-bg: var(--ptc-accent);
  --ptc-badge-text: white;
  --ptc-radius: 1rem;
  --ptc-section-padding: 2rem;
  --ptc-gap: 1.5rem;
}