BubbaLovesCheese
Active member
I made some custom pricing tables for User Upgrades that I want to share. It is just template modifications and CSS, no addons or anything to instal. It's all manual.
This is what the default style looks like, but you can adjust it to your taste.

There are only 2 templates you need to modify.
This is the code for
And place this at the end of your
The important thing is to match up the User_Upgrade ID with the code in the temple. For example:
This is my User Upgrade section in the Admin Panel:

The top one (Bronze) is
The second one (Silver) is
The third one (Gold) is
etc.
You can see the upgrade ID by looking at the URL for each upgrade:

In the above example, Gold has id 3
And that's it.
Test, enjoy, and have fun.
This is what the default style looks like, but you can adjust it to your taste.

There are only 2 templates you need to modify.
account_upgrades
and extra.less
This is the code for
account_upgrades
Code:
<!--------------------------------------->
<!-------- Custom Pricing Table --------->
<!----- Template : account_upgrades ----->
<!--------------------------------------->
<xf:title>{{ phrase('account_upgrades') }}</xf:title>
<xf:wrap template="account_wrapper" />
<xf:js src="xf/payment.js" min="1" />
<!--------------------------------------->
<!-- Pricing Table Title & Description -->
<!--------------------------------------->
<div class="price-header-container">
<h2 class="price-title">Choose Your Upgrade!</h2>
<p class="price-description">
Upgrade your account to enjoy exclusive features (coming soon).
Choose a plan that best suits your needs!
</p>
</div>
<!--------------------------------------------------->
<!-- Displays the User's Current Membership Status -->
<!--------------------------------------------------->
<xf:if is="$purchased is not empty">
<div class="membershipStatus">
<xf:foreach loop="$purchased" value="$upgrade">
<xf:set var="$active" value="{$upgrade.Active.{$xf.visitor.user_id}}" />
<div class="membershipStatus-info">
<!-- Display membership title -->
<p>
<strong>Your current membership:</strong>
<span class="membershipStatus-title">{$upgrade.title}</span>
</p>
<!-- Display expiration date or "never" -->
<p>
<strong>Expires on:</strong>
<span class="membershipStatus-expiry">
<xf:if is="$active.end_date">
<xf:date time="{$active.end_date}" />
<xf:else />
Never
</xf:if>
</span>
</p>
</div>
</xf:foreach>
</div>
</xf:if>
<!---------------------------------->
<!-- Custom Pricing Table Section -->
<!---------------------------------->
<div class="price-table">
<!-------------------------------->
<!-------- Bronze (Free) --------->
<!---- Payment Button Removed ---->
<!----- Links back to Forum ------>
<!-------------------------------->
<div class="price-card">
<div class="price-header">Bronze</div>
<div class="price-value">Free</div>
<ul class="price-features">
<li>Bronze Status</li>
<li>Feature 1</li>
<li>Feature 2</li>
</ul>
<div class="price-footer">
<a href="https://www.yourwebsite.com/"
target="_blank"
class="button button--default">
<xf:fa icon="far fa-undo" aria-hidden="true"/> Back to Main Page
</a>
</div>
</div>
<!------------------------------->
<!-- Silver (User_Upgrade = 2) -->
<!------------------------------->
<div class="price-card">
<div class="price-header">Silver</div>
<div class="price-value">$1.99</div>
<ul class="price-features">
<li>Silver Rank</li>
<li>Feature 1</li>
<li>Feature 2</li>
</ul>
<div class="price-footer">
<!-- Change "2" to whatever the actual payment profile ID is -->
<xf:if is="$available.2">
<xf:form action="{{ link('purchase', $available.2, {'user_upgrade_id': 2}) }}"
ajax="true"
data-xf-init="payment-provider-container">
<div class="inputGroup">
<xf:if is="{{ count($available.2.payment_profile_ids) > 1 }}">
<xf:select name="payment_profile_id">
<xf:option>{{ phrase('(choose_payment_method)') }}</xf:option>
<xf:foreach loop="$available.2.payment_profile_ids" value="$profileId">
<xf:if is="{$profiles.{$profileId}}">
<xf:option value="{$profileId}">
{{ $profiles.{$profileId}.display_title ?: $profiles.{$profileId}.title }}
</xf:option>
</xf:if>
</xf:foreach>
</xf:select>
<span class="inputGroup-splitter"></span>
<xf:button type="submit" icon="purchase">
Upgrade to Silver
</xf:button>
<xf:else />
<xf:button type="submit">
<xf:fa icon="fas fa-chevron-up" aria-hidden="true" /> Upgrade to Silver
</xf:button>
<xf:hiddenval name="payment_profile_id">
{$available.2.payment_profile_ids|first}
</xf:hiddenval>
</xf:if>
</div>
<div class="js-paymentProviderReply-user_upgrade2"></div>
</xf:form>
<xf:else />
<span>Not available</span>
</xf:if>
</div>
</div>
<!----------------------------->
<!-- Gold (User_Upgrade = 3) -->
<!----------------------------->
<div class="price-card">
<div class="price-header">Gold</div>
<div class="price-value">$2.99</div>
<ul class="price-features">
<li>Gold Rank</li>
<li>Feature 1</li>
<li>Feature 2</li>
<li>Feature 3</li>
<li>Feature 4</li>
</ul>
<div class="price-footer">
<!-- Change "3" to whatever the actual payment profile ID is -->
<xf:if is="$available.3">
<xf:form action="{{ link('purchase', $available.3, {'user_upgrade_id': 3}) }}"
ajax="true"
data-xf-init="payment-provider-container">
<div class="inputGroup">
<xf:if is="{{ count($available.3.payment_profile_ids) > 1 }}">
<xf:select name="payment_profile_id">
<xf:option>{{ phrase('(choose_payment_method)') }}</xf:option>
<xf:foreach loop="$available.3.payment_profile_ids" value="$profileId">
<xf:if is="{$profiles.{$profileId}}">
<xf:option value="{$profileId}">
{{ $profiles.{$profileId}.display_title ?: $profiles.{$profileId}.title }}
</xf:option>
</xf:if>
</xf:foreach>
</xf:select>
<span class="inputGroup-splitter"></span>
<xf:button type="submit" icon="purchase">
Upgrade to Gold
</xf:button>
<xf:else />
<xf:button type="submit">
<xf:fa icon="fas fa-chevron-double-up" aria-hidden="true" /> Upgrade to Gold
</xf:button>
<xf:hiddenval name="payment_profile_id">
{$available.3.payment_profile_ids|first}
</xf:hiddenval>
</xf:if>
</div>
<div class="js-paymentProviderReply-user_upgrade3"></div>
</xf:form>
<xf:else />
<span>Current Subscription</span>
</xf:if>
</div>
</div>
<!-------------------------------->
<!------------ Donate ------------>
<!---- Payment Button Removed ---->
<!-- Custom Paypal Button Added -->
<!-------------------------------->
<div class="price-card">
<div class="price-header">Donate</div>
<div class="price-value">Any Amount</div>
<ul class="price-features">
<li>Current Rank</li>
<li>Feature 1</li>
<li>Feature 2</li>
<li>Continuing Support for More Improvements</li>
<li>Knowing You're Awesome!</li>
</ul>
<div class="price-footer">
<a href="https://www.paypal.com/donate/?hosted_button_id=123456789ABC"
target="_blank"
class="button button--default">
<xf:fa icon="fa-heart" aria-hidden="true"/> Donate via PayPal
</a>
</div>
</div>
</div>
<p class="price-description">
Some memberships are recurring monthly subscriptions.
You can change or cancel your subscription at <u>anytime</u> in your PayPal account settings.
</p>
<!---------------------------------------------------------------------------------------------->
<!-- Default XenForo price listing: use it also, or hide it if you only want the boxes above. -->
<!---------------------------------------------------------------------------------------------->
<xf:if contentcheck="true">
<xf:contentcheck>
<xf:if is="$available is not empty">
<div class="block">
<div class="block-container">
<h2 class="block-header">{{ phrase('available_upgrades') }}</h2>
<div class="block-body">
<xf:foreach loop="$available" value="$upgrade">
<xf:formrow rowtype="button"
label="{$upgrade.title}"
hint="{$upgrade.cost_phrase}"
explain="{$upgrade.description|raw}">
<xf:form action="{{ link('purchase', $upgrade, {'user_upgrade_id': $upgrade.user_upgrade_id}) }}"
ajax="true"
data-xf-init="payment-provider-container">
<div class="inputGroup">
<xf:if is="{{ count($upgrade.payment_profile_ids) > 1 }}">
<xf:select name="payment_profile_id">
<xf:option>{{ phrase('(choose_payment_method)') }}</xf:option>
<xf:foreach loop="$upgrade.payment_profile_ids" value="$profileId">
<xf:if is="{$profiles.{$profileId}}">
<xf:option value="{$profileId}">
{{ $profiles.{$profileId}.display_title ?: $profiles.{$profileId}.title }}
</xf:option>
</xf:if>
</xf:foreach>
</xf:select>
<span class="inputGroup-splitter"></span>
<xf:button type="submit" icon="purchase" />
<xf:else />
<xf:button type="submit" icon="purchase" />
<xf:hiddenval name="payment_profile_id">
{$upgrade.payment_profile_ids|first}
</xf:hiddenval>
</xf:if>
</div>
<div class="js-paymentProviderReply-user_upgrade{$upgrade.user_upgrade_id}"></div>
</xf:form>
</xf:formrow>
</xf:foreach>
</div>
</div>
</div>
</xf:if>
<!-------------------------------------------------------------------------->
<!-- Original membership status (moved to top of page) For reference only -->
<!-------------------------------------------------------------------------->
<!--
<xf:if is="$purchased is not empty">
<div class="block">
<div class="block-container">
<h2 class="block-header">{{ phrase('purchased_upgrades') }}</h2>
<ul class="block-body listPlain">
<xf:foreach loop="$purchased" value="$upgrade">
<li>
<div>
<xf:set var="$active" value="{$upgrade.Active.{$xf.visitor.user_id}}" />
<xf:formrow
label="{$upgrade.title}"
hint="{$upgrade.cost_phrase}"
explain="{$upgrade.description|raw}">
<xf:if is="$active.end_date">
{{ phrase('expires:') }}
<xf:date time="{$active.end_date}" />
<xf:else />
{{ phrase('expires_never') }}
</xf:if>
<xf:if contentcheck="true">
<div style="margin-top: 5px;">
<xf:contentcheck>
<xf:if is="$upgrade.length_unit && $upgrade.recurring && $active.PurchaseRequest">
<xf:set var="$provider" value="{$active.PurchaseRequest.PaymentProfile.Provider}" />
{{ $provider.renderChangePayment($active)|raw }}
{{ $provider.renderCancellation($active)|raw }}
</xf:if>
</xf:contentcheck>
</div>
</xf:if>
</xf:formrow>
</div>
</li>
</xf:foreach>
</ul>
</div>
</div>
</xf:if>
-->
</xf:contentcheck>
<xf:else />
<div class="blockMessage">{{ phrase('there_currently_no_purchasable_user_upgrades') }}</div>
</xf:if>
And place this at the end of your
extra.less
CSS:
/*********************************************************************/
/* Below is custom css for the User Account Upgrades (Pricing Table) */
/*********************************************************************/
/* Style the pricing table Header, Title & Description */
.price-header-container {
text-align: center;
margin-bottom: 20px;
}
.price-title {
font-size: 24px;
font-weight: bold;
color: #594825; /* Dark Gold/Brown tone */
margin-bottom: 10px;
}
.price-description {
font-size: 16px;
color: #444; /* Slightly darker gray */
max-width: 600px;
margin: 0 auto; /* Centers the text block */
line-height: 1.5;
}
/* Pricing Table Card Styles */
.price-table {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 1rem; /* space between cards */
margin: 2rem 0;
}
.price-card {
background-color: @xf-contentBg;
border: 2px solid #735414;
border-radius: 20px;
max-width: 300px;
width: 100%;
display: flex;
flex-direction: column;
padding: 1.5rem;
box-shadow: 4px 4px 3px 1px rgba(0, 0, 0, 0.26);
overflow: hidden; /* Ensures proper styling of the header and list */
}
/* Header with colored background */
.price-header {
font-size: 1.5rem;
font-weight: @xf-fontWeightHeavy;
text-align: center;
margin-bottom: 1rem;
padding: 1rem;
background-color: #8d7441; /* Gold/Brown shade */
color: #fff; /* White text */
border-radius: 2px 2px 0 0; /* Rounded corners only at the top */
}
/* Price text */
.price-value {
font-size: 2rem;
font-weight: @xf-fontWeightHeavy;
color: @xf-textColorAttention;
text-align: center;
margin-bottom: 1rem;
}
/* Feature list */
.price-features {
list-style-type: none;
padding: 0;
margin: 0 0 1rem;
flex-grow: 1;
}
/* Alternating row colors */
.price-features li:nth-child(odd) {
background-color: #e6e6e6; /* Darker gray */
}
.price-features li:nth-child(even) {
background-color: #f5f5f5; /* Slightly lighter gray */
}
.price-features li {
padding: 10px;
text-align: center;
}
/* Footer (button area) */
.price-footer {
display: flex;
justify-content: center; /* Center horizontally */
align-items: center; /* Center vertically */
text-align: center;
margin-top: auto; /* Ensures the footer sticks to the bottom */
}
/* Buttons (footer) */
/* Make all buttons the same */
.price-footer a,
.price-footer .button,
.price-footer .inputGroup .button,
.price-footer .button--primary {
display: inline-block;
width: 100%; /* Makes buttons stretch evenly */
max-width: 200px; /* Controls button width */
background-color: #e6c36a; /* Gold button color */
color: #000 !important; /* Black text */
font-size: 14px;
padding: 10px 10px;
text-align: center;
text-decoration: none;
border-radius: 6px;
border: 2px solid #735414; /* Matches card border */
transition: background 0.2s ease-in-out;
}
/* Ensure form-based buttons match */
.price-footer .inputGroup .button,
.price-footer .button--primary {
background-color: #e6c36a !important; /* Ensure uniformity */
color: #000 !important; /* Black text */
border: 2px solid #735414 !important;
}
/* Button hover effect */
.price-footer a:hover,
.price-footer .button:hover,
.price-footer .inputGroup .button:hover,
.price-footer .button--primary:hover {
background-color: #ba943d !important; /* Darker gold on hover */
}
/* Fix alignment for dropdown if multiple payment methods exist */
.price-footer .inputGroup {
display: flex;
justify-content: center;
gap: 0.5rem;
align-items: center;
padding-top: 10px;
}
/* Remove the header ONLY on the Account Upgrades page */
[data-template="account_upgrades"] .p-body-header {
display: none !important;
}
/*****************************/
/* END OF PRICING TABLE CODE */
/*****************************/
/***********************************************************************/
/* Style for Displaying the Users Current Membership Status and Expiry */
/***********************************************************************/
.membershipStatus {
text-align: center;
padding: 1rem;
margin: 1rem auto;
max-width: 600px;
background-color: @xf-contentBg;
border: 2px solid #735414;
border-radius: 4px;
box-shadow: 2px 2px 4px 1px rgba(0, 0, 0, 0.26);
}
.membershipStatus-info p {
margin: 0.5rem 0;
}
.membershipStatus-title {
font-weight: bold;
margin-left: 0.25em;
color: #333;
}
.membershipStatus-expiry {
margin-left: 0.25em;
color: #444;
font-weight: 600;
}
The important thing is to match up the User_Upgrade ID with the code in the temple. For example:
This is my User Upgrade section in the Admin Panel:

The top one (Bronze) is
id = 1
which I have disable (just cuz it happened that way), so I don't use ID=1
anywhere in my template.The second one (Silver) is
id = 2
. So I matched that up with Silver (User_Upgrade = 2) in the template.The third one (Gold) is
id = 3
. So I matched that up with Gold (User_Upgrade = 3) in the template.etc.
You can see the upgrade ID by looking at the URL for each upgrade:

In the above example, Gold has id 3
And that's it.
Test, enjoy, and have fun.