Pricing Table
Pricing Table is all over the internet with a lot of different patterns. I tried to cover the archetype of this kind of component here.
Defining our Pricing Table needs
As usual, I'll define some goals so that the CSS solution does make sense to you. What I need for this pricing table is:
- Same Column Height already seen in Flexbox.ninja
- All call to action at the same level
- One highlighted columns
- Flexible amount of items
- Keep the HTML code as simple and accessible as possible, as usual 😘
Let's code our Pricing Table
Our HTML basics
Here is my piece of HTML. Just add any item you want PT-Item
and the class .is-highlighted
to the item you want to highlight.
<section class="Pricing-Table">
<article class="PT-Item">
<header class="PT-Heading">
<h2 class="PT-Title">Free</h2>
<p class="PT-Subtitle">Forever, promise.</p>
</header>
<ul class="PT-Features">
<li class="PT-Feature">Unlimited Items</li>
<li class="PT-Feature">2 Team Members</li>
<li class="PT-Feature">3 Projects</li>
<li class="PT-Feature">Cloud Storage (2Gb)</li>
</ul>
<div class="PT-Footer">
<p class="PT-Price">
<small>$</small>
<span class="PT-nb">0</span>
<small>/month</small>
</p>
<p class="PT-Trial">14-day money back guarantee</p>
<p class="PT-CTA">
<a href="#" class="button">Buy this one</a>
</p>
</div>
</article>
<!-- ... repeat -->
</section>
I used a ul
element for the list of features, because it's a list so it makes sense using semantic HTML. Same for the rest of the HTML element choices here 😊
You can totally use Microdata (FR) in this case to improve the code of your pricings.
CSS to flex the Pricing Table
Ok now what I need is to have a two dimension flex context. The first one is for making the items next to each other and stretch by default. The second is to put the content of each item in a column direction to put the footer at the very bottom of an offer.
/**
* The container is in a Flex layout
* with a gap of 24px
*/
.Pricing-Table {
--gap: 24px;
--nb-items: 3;
/* I used CSS Variables here to
* facilitate the calculation
* of width suggestion later.
*/
display: flex;
justify-content: center;
/* Make it wrap in case we have too many items */
flex-wrap: wrap;
gap: var(--gap);
/* Just design purpose */
width: 1040px;
max-width: 100%;
padding: 32px;
}
/**
* Below a bit of magic to count
* the number of items.
* I stopped over 5 on purpose.
*/
.PT-Item:nth-last-child(4):first-child,
.PT-Item:nth-last-child(4):first-child ~ .PT-Item {
--nb-items: 4;
}
.PT-Item:nth-last-child(5):first-child,
.PT-Item:nth-last-child(5):first-child ~ .PT-Item {
--nb-items: 5;
}
/**
* Flex direction column to put our
* footer at the very bottom.
*/
.PT-Item {
/* Preparing for themed item */
--primary: #F34A4E;
display: flex;
flex-direction: column;
flex-grow: 1;
/* Be careful with max-content value */
min-width: max-content;
background: white;
border-radius: 24px;
overflow: hidden;
/**
* The calc here is a way to keep thing out
* of the wrapping behavior for a while.
* Above 5 items it stops and go back
* to --nb-items = 3
*/
flex-basis: calc( calc(100% - (var(--gap) * var(--nb-items)) )/var(--nb-items) );
}
.PT-Item.is-highlighted {
/* Preparing for highlighted item */
--primary: #1FA19C;
transform: scale(1.05);
}
/**
* Most of the rest is for
* decorative purpose.
*/
.PT-Item > * {
padding: 24px;
}
.PT-Item p,
.PT-Item ul,
.PT-Item h2 {
margin-top: 0;
margin-bottom: 0;
}
.PT-Heading {
text-align: center;
color: white;
background: var(--primary);
}
.PT-Feature {
font-size: .875em;
padding: 8px 0;
list-style: none;
}
.PT-Feature + .PT-Feature {
border-top: 1px solid #eee;
}
.PT-Footer {
padding-top: 0;
margin-top: auto;
text-align: center;
}
.PT-Price {
font-size: 2.5rem;
color: var(--primary);
}
.PT-Price small {
margin: 0 -.25em;
font-size: 1rem;
color: #777;
}
.PT-Trial {
font-size: .75em;
color: #777;
}
The magic parts here are the wrap
option and the fact that .PT-Item
width is determined by the flex-basis
calculation. Above 5 items I stopped counting on purpose, but feel free to adjust this code for your needs.
There is no need for responsive complement here since the entire component is wrapping naturally.
Have fun!