Plan Change Preview — Free Upgrade/Downgrade Feature Diff (HTML/CSS/JS)
Plan Change Preview (Upgrade/Downgrade) · Pricing · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Plan Change Preview — What You Actually Gain and Lose, Computed From Real Feature Sets

"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.
Build with AI
Build, Understand, Optimize, and Extend It With AI
You don't have to design the feature-diffing algorithm from scratch. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly how diffPlans() uses Array.filter combined with indexOf to compute gained, lost, and unchanged features as three genuine set operations over two plans' feature arrays, and why this generic approach correctly handles an upgrade, a downgrade, and a same-plan "change" without ever needing a hardcoded per-pair diff list. The same assistant can help you verify correctness — ask it to trace through two specific plans in your own PLANS object and confirm the gained/lost counts match what you'd expect by manually comparing the arrays — or extend the widget: ask how to group the gained/lost lists by feature category instead of a flat list, how to add a price-delta line showing the dollar difference alongside the feature diff, or how to highlight which lost features are the ones most likely to matter (e.g. flagged as "critical") so a downgrade warning can be more prominent for those. Treat the code less like a finished artifact and more like a starting point for a conversation.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build a "plan change preview" widget in plain HTML, CSS, and JavaScript with no framework or library, showing what a user gains and loses when switching between pricing plans.
Requirements:
- Define at least four plans as data, each with a label and an array of feature-name strings — design the feature lists so some plans share features and each plan also has features unique to it (a realistic tiered structure, not every plan sharing everything or nothing).
- Add a way to pick a "current" plan and a "target" plan (a current plan constant plus a dropdown to pick the target is fine).
- Write a pure function that takes two plans' feature arrays and computes three results using real set-comparison logic (e.g. Array.filter combined with indexOf or includes) — features present in the target but not the current plan ("gained"), features present in the current plan but not the target ("lost"), and features present in both ("unchanged") — do NOT hardcode a separate gained/lost list for each possible pair of plans; the same function must correctly handle any pair.
- Render the gained features as a list with a "+" visual marker and the lost features as a list with a "−" visual marker, updating live whenever the target plan selection changes.
- Verify by hand (in a comment or your own testing) that an upgrade (moving to a plan with more features) produces a non-empty gained list and an empty (or smaller) lost list, that a downgrade produces the reverse, and that selecting the current plan as its own target produces empty gained and lost lists with everything showing as unchanged.
- Add empty-state text for the gained and lost lists (e.g. "No new features on this plan") so an empty result reads as an intentional answer rather than a blank/broken area, and show a summary of how many features are unchanged between the two plans.Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.
Step by step
How to Use
- 1Paste HTML, CSS, and JSPro → Team renders with 3 gained features and 0 lost.
- 2Change the target planPick Starter — the diff recomputes to show what you'd lose.
- 3Pick the current plan as targetBoth lists show empty-state text; nothing actually changes.
- 4Read the unchanged countStates exactly how many features carry over, by name.
- 5Verify a diff by handCompare two plans' feature arrays and confirm gained/lost match.
- 6Wire up real plansEdit the PLANS object with your actual tiers and feature lists.
Real-world uses
Common Use Cases
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.