How to build a pricing slider — HTML & Vanilla JS
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