How to build a pricing slider — HTML & Vanilla JS

Pasquale Vitiello
Cruip
Published in
6 min readMar 16, 2020

If you are selling pay as you go or subscription plans, there might be chances that you need a landing page with a pricing table controlled by a range slider — just like in the example below 👇

Let’s start with the HTML and JavaScript version, then we will cover React and Vue ones in the next articles!

Creating the HTML structure

I’ve created a very basic HTML structure, with some ready-made CSS from the Cruip framework.

<div class="pricing">    <div class="pricing-slider">
<label class="form-slider">
<span>How many users do you have?</span>
<input type="range" />
</label>
<div class="pricing-slider-value"></div>
</div>
<div class="pricing-items">
<div class="pricing-item">
<div class="pricing-item-inner">
<div class="pricing-item-content">
<div class="pricing-item-header">
<div class="pricing-item-title">Basic</div>
<div class="pricing-item-price">
<span class="pricing-item-price-currency">$</span>
<span class="pricing-item-price-amount">13</span>
<span class="pricing-item-price-after">/m</span>
</div>
</div>
<div class="pricing-item-features">
<ul class="pricing-item-features-list">
<li class="is-checked">Excepteur sint occaecat</li>
<li class="is-checked">Excepteur sint occaecat</li>
<li class="is-checked">Excepteur sint occaecat</li>
<li>Excepteur sint occaecat</li>
<li>Excepteur sint occaecat</li>
</ul>
</div>
</div>
<div class="pricing-item-cta">
<a class="button" href="#">Buy Now</a>
</div>
</div>
</div>
</div>
</div>

Notice that we have input ⬇️ and output ⬆️ elements.

Input elements

  • The <input type="range" /> element, i.e. the slider control
  • The <div class="pricing-slider-value"> element, into which we will write the current slider value

Output elements

We can have multiple pricing tabs, which means multiple outputs. Each output consists of a <div class="pricing-item-price"> element, that contains 3 more elements:

  • <span class="pricing-item-price-currency"> for the currency sign
  • <span class="pricing-item-price-amount"> for the amount
  • <span class="pricing-item-price-after"> for any other information, such as the billing period

And here is the result 👇

Shaping input and output data

We need to design our data scheme now. I’ve defined a range of slider values (input) and the corresponding price values (output).

Go on adding input and output data to HTML via data attributes.

Input data 👇

<input
type="range"
data-price-input='{
"0": "1,000",
"1": "1,250",
"2": "1,500",
"3": "2,000",
"4": "2,500",
"5": "3,500",
"6": "6,000",
"7": "15,000",
"8": "50,000",
"9": "50,000+"
}'
/>

Output data looks a little different for structure, since each value is not a string, but an array of strings.

<div
class="pricing-item-price"
data-price-output='{
"0": ["", "Free", ""],
"1": ["$", "13", "/m"],
"2": ["$", "17", "/m"],
"3": ["$", "21", "/m"],
"4": ["$", "25", "/m"],
"5": ["$", "42", "/m"],
"6": ["$", "58", "/m"],
"7": ["$", "117", "/m"],
"8": ["$", "208", "/m"],
"9": ["", "Contact us", ""]
}'
>

Defining JavaScript variables

Since we might want to display more than one pricing slider on a page, let’s collect all elements having pricing-slider as a class, and loop through them.

const pricingSliders = document.querySelectorAll(".pricing-slider");if (pricingSliders.length > 0) {
for (let i = 0; i < pricingSliders.length; i++) {
const pricingSlider = pricingSliders[i];
}
}

Now that we have our pricing slider defined by a constant, we can move forward with storing elements and data, for both input and output.

To do that, we are going to create:

  • a pricingInput object that contains stuff dealing with the range slider (the input)
  • a pricingOutput variable, that contains output elements and data. It's an array because, as previously said, we might have more than one output 😉
if (pricingSliders.length > 0) {
for (let i = 0; i < pricingSliders.length; i++) {
const pricingSlider = pricingSliders[i];
// Build the input object
const pricingInput = {
el: pricingSlider.querySelector("input")
};
pricingInput.data = JSON.parse(
pricingInput.el.getAttribute("data-price-input")
);
pricingInput.currentValEl = pricingSlider.querySelector(
".pricing-slider-value"
);
pricingInput.thumbSize = parseInt(
window
.getComputedStyle(pricingInput.currentValEl)
.getPropertyValue("--thumb-size"),
10
);
// Build the output array
const pricingOutputEls = pricingSlider.parentNode.querySelectorAll(
".pricing-item-price"
);
const pricingOutput = [];
for (let i = 0; i < pricingOutputEls.length; i++) {
const pricingOutputEl = pricingOutputEls[i];
const pricingOutputObj = {};
pricingOutputObj.currency = pricingOutputEl.querySelector(
".pricing-item-price-currency"
);
pricingOutputObj.amount = pricingOutputEl.querySelector(
".pricing-item-price-amount"
);
pricingOutputObj.after = pricingOutputEl.querySelector(
".pricing-item-price-after"
);
pricingOutputObj.data = JSON.parse(
pricingOutputEl.getAttribute("data-price-output")
);
pricingOutput.push(pricingOutputObj);
}
}
}

Let’s see what’s inside these objects 📦

Setting range slider attributes

Now we can proceed with setting the range slider min, max, and value attributes.

if (pricingSliders.length > 0) {
for (let i = 0; i < pricingSliders.length; i++) {
const pricingSlider = pricingSliders[i];
// [ ... previously defined variables ... ] // set input range min attribute (0)
pricingInputEl.setAttribute("min", 0);
// set input range max attribute (9, i.e. the number of values)
pricingInputEl.setAttribute("max", Object.keys(priceInput).length - 1);
// initial slider value (0, or any other value if assigned via HTML)
!pricingInputEl.getAttribute("value") &&
pricingInputEl.setAttribute("value", 0);
}
}

Great! We have a range slider whose values go from 0 to 9 🙌

The next step is outputting the slider value (e.g. 1,000) that corresponds to the current range value (e.g. 0), into the <div class="pricing-slider-value"> element.

To do that, we need to create a function to be invoked every time a user interacts with the slide. As obvious, we need to pass our input and output objects as arguments

function handlePricingSlide(input, output) {
// output the current slider value
if (input.currentValEl)
input.currentValEl.innerHTML = input.data[input.el.value];
}

Let’s call the function 📢

if (pricingSliders.length > 0) {
for (let i = 0; i < pricingSliders.length; i++) {
const pricingSlider = pricingSliders[i];
// [ ... previously defined variables ... ]
// [ ... previous range slider attributes assignment ... ]
handlePricingSlider(pricingInput, pricingOutput);
window.addEventListener("input", function() {
handlePricingSlider(pricingInput, pricingOutput);
});
}
}

And here is the result 👇

Binding input and output data with JavaScript

We have a working range slider, but it is still disconnected from the visualized price. It’s time to bind input slider values with output price data.

function handlePricingSlide(input, output) {
// output the current slider value
if (input.currentValEl)
input.currentValEl.innerHTML = input.data[input.el.value];
// update prices
for (let i = 0; i < output.length; i++) {
const outputObj = output[i];
if (outputObj.currency) outputObj.currency.innerHTML = outputObj.data[input.el.value][0];
if (outputObj.amount) outputObj.amount.innerHTML = outputObj.data[input.el.value][1];
if (outputObj.after) outputObj.after.innerHTML = outputObj.data[input.el.value][2];
}
}

Adjusting the slider value element position

Almost there. 🏁 We want the slider value to be following the slider thumb.

Let’s create a function calculating the left value to be applied to the slider value element.

function handleSliderValuePosition(input) {
const multiplier = input.el.value / input.el.max;
const thumbOffset = input.thumbSize * multiplier;
const priceInputOffset =
(input.thumbSize - input.currentValEl.clientWidth) / 2;
input.currentValEl.style.left =
input.el.clientWidth * multiplier - thumbOffset + priceInputOffset + "px";
}

The function determines the proper slider value position, so that the element is horizontally aligned with the slider thumb. Here is a visual representation of what the function does 👇

Notice that the thumb size value is parsed with the getComputedStyle() method (see the paragraph where we defined the JS variables). That way I can change the thumb size in the CSS, without having to change anything in my JavaScript file.

Setting a default slider value

In case you want to set an initial slider value other than Free, you just need to add a value="n" attribute to the range slider.

For example, <input type="range" value="1" /> will return a range slider with 1,000 as initial slider value.

Conclusions

Here is the final result again. Click on Open Sandbox to see the full code.

I hope you enjoyed this tutorial. If you want to see this in action here is a landing page template where it’s implemented 👉 Surface

--

--