Skip to content

Instantly share code, notes, and snippets.

@trenkwill
Last active April 29, 2025 19:39
Show Gist options
  • Save trenkwill/a9d31142ac15cebde3fb1fbcc236c0ca to your computer and use it in GitHub Desktop.
Save trenkwill/a9d31142ac15cebde3fb1fbcc236c0ca to your computer and use it in GitHub Desktop.
Shopify Dawn facets price range slider with 2 handles
// Add styling to the CSS file
.facets__price input[type='range'] {
-webkit-appearance: none;
padding: 0;
font: inherit;
outline: none;
color: rgb(var(--color-foreground));
opacity: .8;
background: rgb(var(--color-foreground));
box-sizing: border-box;
transition: opacity .2s;
cursor: pointer;
height: 1px;
}
.facets__price input[type='range']::-webkit-slider-thumb {
cursor: ew-resize;
background: rgb(var(--color-foreground));
color: rgb(var(--color-foreground));
height: 20px;
width: 20px;
border-radius: 50%;
cursor: pointer;
-webkit-appearance: none;
}
.facets__price input[type="range"]::-moz-range-progress {
background: rgb(var(--color-foreground));
}
.facets__price input[type="range"]::-moz-range-track {
background: rgb(var(--color-foreground));
}
/* IE*/
.facets__price input[type="range"]::-ms-fill-lower {
background: rgb(var(--color-foreground));
}
.facets__price input[type="range"]::-ms-fill-upper {
background: rgb(var(--color-foreground));
}
.facets__price .range-wrap {
position: relative;
margin: 0 auto 3rem;
}
.facets__price .range {
width: 100%;
}
.facets__price .bubble {
position: absolute;
left: 50%;
transform: translateX(-50%);
}
// replace the PriceRange class in facets.js
class PriceRange extends HTMLElement {
constructor() {
super();
this.querySelectorAll('input')
.forEach(element => element.addEventListener('change', this.onRangeChange.bind(this)));
// commented out in order to make range sliders work properly
// this.setMinAndMaxValues();
this.querySelectorAll('.range-wrap').forEach(wrap => {
const range = wrap.querySelector(".range");
const bubble = wrap.querySelector(".bubble");
range.addEventListener("input", () => {
this.setBubble(range, bubble);
});
this.setBubble(range, bubble);
});
}
onRangeChange(event) {
this.adjustToValidValues(event.currentTarget);
this.setBubble(event.currentTarget, bubble);
// commented out in order to make range sliders work properly
// this.setMinAndMaxValues();
}
setBubble(range, bubble) {
const val = range.value;
const min = range.min ? range.min : 0;
const max = range.max ? range.max : 100;
const newVal = Number(((val - min) * 100) / (max - min));
bubble.innerHTML = val;
// Sorta magic numbers based on size of the native UI thumb
bubble.style.left = `calc(${newVal}% + (${8 - newVal * 0.15}px))`;
}
setMinAndMaxValues() {
const inputs = this.querySelectorAll('input');
const minInput = inputs[0];
const maxInput = inputs[1];
if (maxInput.value) minInput.setAttribute('max', maxInput.value);
if (minInput.value) maxInput.setAttribute('min', minInput.value);
if (minInput.value === '') maxInput.setAttribute('min', 0);
if (maxInput.value === '') minInput.setAttribute('max', maxInput.getAttribute('max'));
}
adjustToValidValues(input) {
const value = Number(input.value);
const min = Number(input.getAttribute('min'));
const max = Number(input.getAttribute('max'));
if (value < min) input.value = min;
if (value > max) input.value = max;
}
}
// replace the <price-range> element that contains number inputs with range inputs
<price-range class="facets__price">
<div class="range-wrap">
<input
class="range"
name="{{ filter.min_value.param_name }}"
id="Filter-{{ filter.label | escape }}-GTE"
{% if filter.min_value.value %}
value="
{%- if uses_comma_decimals -%}
{{ filter.min_value.value | money_without_currency | replace: '.', '' | replace: ',', '' }}
{% else %}
{{ filter.min_value.value | money_without_currency | replace: ',', '' }}
{% endif %}
"
{% else %}
value="0"
min="0"
step="0.5"
max="
{%- if uses_comma_decimals -%}
{{ filter.range_max | divided_by: 2 | money_without_currency | replace: '.', '' | replace: ',', '' }}
{% else %}
{{ filter.range_max | divided_by: 2 | money_without_currency | replace: ',', '' }}
{% endif %}
"
{% endif %}
type="range"
>
<output class="bubble"></output>
</div>
<div class="range-wrap">
<input
class="range"
name="{{ filter.max_value.param_name }}"
id="Filter-{{ filter.label | escape }}-LTE"
step="0.5"
{% if filter.max_value.value %}
value="
{%- if uses_comma_decimals -%}
{{ filter.max_value.value | money_without_currency | replace: '.', '' | replace: ',', '' }}
{% else %}
{{ filter.max_value.value | money_without_currency | replace: ',', '' }}
{% endif %}
"
{% else %}
value="{{ filter.range_max | money_without_currency | replace: ',', '' }}"
min="{{ filter.range_max | divided_by: 2 | money_without_currency | replace: ',', '' }}"
{% endif %}
value="{{ filter.max_value.value | money_without_currency | replace: ',', '' }}"
type="range"
max="{{ filter.range_max | money_without_currency | replace: ',', '' }}"
>
<output class="bubble"></output>
</div>
</price-range>
@esbasicamente
Copy link

The price number under slider is not showing on dawn version 14.0.0, can u check please?

@joeybab3
Copy link

Seems that the var bubble is not defined on line 27:
this.setBubble(event.currentTarget, bubble);
Within the onRangeChange(event) function.

@MTKdesu
Copy link

MTKdesu commented Apr 29, 2025

Even the left handle has a max value of the medium number of the highest price, it can only move up to 100. Do you know the reason?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment