No Time Dad

A blog about web development written by a busy dad

Product Price Panel Example

Intro

Inspired by some of the great pricing panel examples on TailwindUI, I came up with a quick example of a single panel that is pretty simple to make and looks good. Similar to some other recent examples I have done, this is just a plain css panel.

Getting Started

Our pricing panel is going to contain two sections; an upper section that has the pricing details and the call-to-action button, and a lower section that lists out the included features for the price point.

Generally speaking, when I am starting a new component I like to add most of the copy and content to whatever I am building first. I then have as get a rough idea of how the spacing should look for each element. After that is done, I’ll go back and add more styles to specific elements and finalize the spacing.

So, the html and css below gives us our basic structure and spacing with the content. I’ll be using heroicons for the svg icons. You can just copy and paste in the svg element from their site, which is really handy.

:root {
  --shared-border: 2px solid rgba(229, 231, 235, 1);
}

.price_panel__container {
  border-radius: 0.5rem;
  border: var(--shared-border);
  max-width: 300px;
}

.price_panel__upper_header_text {
  padding: 0;
  margin: 0;
}

.price_panel__lower {
  border-top: var(--shared-border);
}

.price_panel__lower_included_row {
  display: flex;
  align-items: center;
}

.price_panel__lower_icon {
  width: 1.5rem;
  height: 1.5rem;
}
<div class="price_panel__container">
  <div class="price_panel__upper">
    <h3 class="price_panel__upper_header_text">Enterprise</h3>
    Everything you need for your big company
    <div class="price_panel__price_text">
      $99/mo
    </div>
    <button>Buy Enterprise</button>
  </div>
  <div class="price_panel__lower">
    Includes
    <div class="price_panel__lower_included_row">
      <svg class="price_panel__lower_icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
      </svg>
      24/7 Support
    </div>
    <div class="price_panel__lower_included_row">
      <svg class="price_panel__lower_icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
      </svg>
      1,000 Emails
    </div>
    <div class="price_panel__lower_included_row">
      <svg class="price_panel__lower_icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
      </svg>
      Unlimited Storage
    </div>
  </div>
</div>

Enterprise

Everything needed for large companies
$99/mo
Inlcudes
24/7 Support
1,000 Emails
Unlimited Storage

So there is our starting point. It is pretty basic and not much to look at, but it is a good start to build on. I ended up creating a custom css property for the border called --shared-border since it is used as the panel outline as well as a top-border to break up the upper and lower sections.

One of my favorite recent discoveries (used above in the rows) is the magic of align-items: center alongside display: flex, which perfectly aligns text and icons (or any element) in a row. I am not sure how I made it this far as a web developer without using that. For some reason it is just really pleasing to have things line up perfectly like that.

Finalizing the Spacing and Styling

Now we can work on finalizing the spacing and getting a sense of what kind of styling we want each element to have. I don’t want to go overboard with styling the elements in this example, but they should also still look presentable.

:root {
  --shared-border: 2px solid rgba(229, 231, 235, 1);
}

.price_panel__container {
  border-radius: 0.5rem;
  border: var(--shared-border);
  max-width: 300px;
  height: 500px;
}

.price_panel__upper {
  padding: 1rem;
}

.price_panel__upper_header_text {
  padding: 0;
  margin: 0;
  padding-bottom: 1.5rem;
  font-size: 32px;
}

.price_panel__price_text {
  font-size: 52px;
  padding-bottom: 1.5rem;
}

.price_panel__time {
  font-size: 18px;
}

.price_panel__buy_button {
  width: 100%;
  padding: 1rem 1rem;
  border-radius: 0.5rem;
  background-color: darkblue;
  color: white;
  font-weight: bold;
}

.price_panel__buy_button:hover {
  opacity: 0.8;
  cursor: pointer;
}

.price_panel__buy_button:focus {
  border-radius: 0.5rem;
}

.price_panel__lower {
  border-top: var(--shared-border);
  padding: 1rem;
}

.price_panel__lower_included_row {
  display: flex;
  align-items: center;
}

.price_panel__lower_icon {
  width: 1.5rem;
  height: 1.5rem;
  margin-right: 0.5rem;
  color: darkblue;
  opacity: 0.6;
}

.price_panel__upper_header_subtext {
  padding-bottom: 1rem;
}

.price__panel__lower_header_text {
  padding-bottom: 0.5rem;
}
<div class="price_panel__container">
  <div class="price_panel__upper">
    <h3 class="price_panel__upper_header_text">Enterprise</h3>
    <div class="price_panel__upper_header_subtext">Everything needed for large companies</div>
    <div class="price_panel__price_text">
      $99
      <span class="price_panel__time">/mo</span>
    </div>
    <button class="price_panel__buy_button">Buy Enterprise</button>
  </div>
  <div class="price_panel__lower">
    <div class="price__panel__lower_header_text">Includes</div>
    <div class="price_panel__lower_included_row">
      <svg class="price_panel__lower_icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
      </svg>
      24/7 Support
    </div>
    <div class="price_panel__lower_included_row">
      <svg class="price_panel__lower_icon" ${styles.price_panel__lower_icon__margin}`} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
      </svg>
      1,000 Emails
    </div>
    <div class="price_panel__lower_included_row">
      <svg class="price_panel__lower_icon" ${styles.price_panel__lower_icon__margin}`} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
      </svg>
      Unlimited Storage
    </div>
  </div>
</div>

Enterprise

Everything needed for large companies
$99/mo
Includes
24/7 Support
1,000 Emails
Unlimited Storage

So this is our finished code, and I think the pricing panel looks pretty good. It wasn’t a huge amount of code by any means, and we didn’t need any special libraries or advanced css.

The major changes from the Getting Started section here involved spacing and sizing, as well as adding styling to the button. I also increased the size of the dollar amount and added a new class for month text. Some of the button styles are “nice to haves” in my opinion, but they definitely add a little extra to the panel.

I probably spent the most time on the button. I think styling buttons in general is pretty tedious. The focus ring is pretty annoying to style and seems to vary by browser. It looks fine on Firefox, but sort of janky on Chrome since the border-radius property doesn’t really seem to be working there. Something to fix later I think.

One important change I made in this version was to increase the height of the price panel itself. You’re likely to have several of these panels next to each other and different prices will likely have a different list of features they include. If you don’t set the height property then you could end up with panels at all different heights depending on what is in the “Includes” section.