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!