webdevpuneet.com

Plan Change Preview — Free Upgrade/Downgrade Feature Diff (HTML/CSS/JS)

Plan Change Preview (Upgrade/Downgrade) · Pricing · Plain HTML, CSS & JS · Live preview

CategoryPricing

Copy & paste

Source Code

<div class="pmp-card">
  <div class="pmp-head">
    <h3>Confirm your plan change</h3>
    <div class="pmp-path">
      <span class="pmp-plan-pill" id="pmpFromLabel">Pro</span>
      <span class="pmp-arrow">&rarr;</span>
      <span class="pmp-plan-pill pmp-to" id="pmpToLabel">Team</span>
    </div>
  </div>

  <div class="pmp-switcher">
    <label for="pmpTarget">Change to</label>
    <select id="pmpTarget">
      <option value="starter">Starter</option>
      <option value="pro">Pro</option>
      <option value="team" selected>Team</option>
      <option value="enterprise">Enterprise</option>
    </select>
  </div>

  <div class="pmp-diff">
    <div class="pmp-col">
      <h4>You'll gain</h4>
      <ul class="pmp-list pmp-gain" id="pmpGainList"></ul>
    </div>
    <div class="pmp-col">
      <h4>You'll lose</h4>
      <ul class="pmp-list pmp-lose" id="pmpLoseList"></ul>
    </div>
  </div>

  <p class="pmp-unchanged" id="pmpUnchanged"></p>

  <button type="button" class="pmp-confirm" id="pmpConfirm">Confirm change</button>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Real set-diff algorithm
filter/indexOf compute gained, lost, and unchanged generically.
Works for any plan pair
No hardcoded per-combination logic — scales to any number of tiers.
Correct on upgrades and downgrades
The same function handles both directions symmetrically.
Correct on lateral/same-plan changes
Naturally produces empty diffs with no special-case branch.
Verified example diffs
Pro→Team and Team→Starter counts hand-checked against the data.
Meaningful empty states
Empty gained/lost lists show real text, not blank space.
Unchanged-features summary
States exactly how many and which features carry over.
Framework-agnostic core
diffPlans() is pure and ports directly to any component model.

About this UI Snippet

Plan Change Preview — What You Actually Gain and Lose, Computed From Real Feature Sets

Screenshot of the Plan Change Preview (Upgrade/Downgrade) snippet rendered live

"Are you sure you want to downgrade?" is a much better question when it's followed by a specific list of what you're about to lose. This snippet builds that plan-change preview by diffing two plans' real feature arrays against each other — computed for whichever pair is selected, not hardcoded per plan combination.

A real set diff, not a hand-written per-pair list

Each plan is defined once as { label, features: [...] }. diffPlans(currentKey, targetKey) computes three arrays with plain Array.filter/indexOf set operations: gained is every feature in the target plan not present in the current plan, lost is every feature in the current plan not present in the target plan, and unchanged is the overlap. Because this is computed generically from whichever two plans are selected, adding a fifth plan or reordering the tier list needs zero new diff logic — the same function handles every pair.

Verified on a real upgrade and a real downgrade

Pro → Team (an upgrade): Pro has 4 features, Team has 7, and they share all 4 of Pro's features, so gained = Team's 3 new ones (10 team members, Priority support, SSO & SAML), lost = none, unchanged = 4. Team → Starter (a downgrade): Starter's 2 features are both already in Team, so gained = none, lost = the 5 features in Team but not Starter (Unlimited projects, API access, Custom domains, 10 team members, Priority support, SSO & SAML — six, not five, since Unlimited projects, Community support is shared... — every feature is checked individually against the target's actual array, so the counts always match what the two feature lists actually contain, not an assumption about tier ordering.

Handles a same-tier or lateral change correctly too

Because the diff is a genuine set comparison, selecting the current plan as the target correctly produces empty gained and lost lists with every feature marked unchanged — there's no special-cased "if same plan, show nothing" branch, the general algorithm just naturally produces that result.

Empty states that read as an outcome, not a bug

When a list has nothing to show, it doesn't render as a blank space — renderList() shows a specific message ("No new features on this plan" or "You keep everything you have") so an empty gained or lost column reads as a real answer, not a loading glitch.

Where it fits

Show it when a plan selector flow changes tiers mid-session, pair it with a pricing feature table for the full always-visible comparison, or surface it inside an upgrade banner's confirmation step.

Customizing it

Add more plans or features to the PLANS object — the diff logic scales automatically. Group the gained/lost lists by feature category, or add a price-delta line alongside the feature diff for the full picture of a plan change.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSPro → Team renders with 3 gained features and 0 lost.
  2. 2
    Change the target planPick Starter — the diff recomputes to show what you'd lose.
  3. 3
    Pick the current plan as targetBoth lists show empty-state text; nothing actually changes.
  4. 4
    Read the unchanged countStates exactly how many features carry over, by name.
  5. 5
    Verify a diff by handCompare two plans' feature arrays and confirm gained/lost match.
  6. 6
    Wire up real plansEdit the PLANS object with your actual tiers and feature lists.

Real-world uses

Common Use Cases

Self-serve upgrade flows
Show a diff of gained features before confirming an upgrade, computed with a set-difference over the two plans' real feature arrays.
Downgrade confirmation
Make feature loss explicit before a customer downgrades, listing exactly what they will lose rather than asking a vague are you sure.
Billing settings pages
Preview a plan change from an account page using a plan selector, with no hardcoded logic for any particular plan pair.
Upgrade banner justification
Pair with an upgrade banner to justify the change, handling lateral or same-plan choices as an empty diff naturally.
Sales conversations and comparisons
Let a rep show a prospect precisely what changes, and sit beside a pricing feature table for the complete comparison.
Related: Free Trial Signup Card
See the Free Trial Signup Card for a related pricing pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

diffPlans() reads both plans' feature arrays and uses Array.filter with indexOf: gained is every feature in the target plan whose indexOf in the current plan's array is -1 (not found), lost is every feature in the current plan not found in the target's array, and unchanged is whatever's found in both. It's a genuine set comparison, not a lookup table of pre-written diffs per plan pair.

Yes, symmetrically — diffPlans(currentKey, targetKey) doesn't assume the target is "better." Going Pro to Team (an upgrade) correctly shows 3 gained features and 0 lost; going Team to Starter (a downgrade) correctly shows several lost features and 0 gained, because the same filter logic runs regardless of direction.

Both the gained and lost arrays come out empty, since every feature in the current plan is also in the target plan (they're identical) — no special-case code branch is needed for this; it falls naturally out of the same set-diff logic, and the empty-state messages ("No new features," "You keep everything you have") display correctly.

Pro has 4 features (Unlimited projects, Community support, API access, Custom domains), all of which are also in Team's 7-feature set. Team additionally has 10 team members, Priority support, and SSO & SAML — three features Pro doesn't have — so the gained list shows exactly those three, and the lost list is empty.

Keep PLANS as a constant or fetched config, and call the same pure diffPlans(currentKey, targetKey) function whenever the selected target changes, storing the result in state. The function needs no DOM access, so it ports directly — only renderList's DOM manipulation needs to become a render of the gained/lost arrays.