You could create an element that would go on top of the range slider. I have an example where the .range-text element is the same size as the slider so it feels the same as the regular input when moving it, and the text/position updates via JS. Hopefully it gives you an idea of how you can create something to solve for your needs
const rangeInput = document.querySelector('input[type="range"]');
const rangeText = document.querySelector('.range-text');
rangeInput.addEventListener('change', (e) => {
let newVal = e.target.value;
let negNewVal = -1 * newVal;
rangeText.style.left = (newVal + '%'); //Set range left position
rangeText.style.transform = 'translate(' + negNewVal + '%, 2px)'; //Set range translate to correct
rangeText.innerHTML = newVal; //Set range text equal to input position
})
.input-container {
position: relative;
display: flex;
background: black;
width: 100%;
padding: 0;
}
input[type="range"] {
appearance: none;
background-color: white;
width: 100%;
height: 20px;
padding: 0;
}
input[type="range"]::-webkit-slider-thumb {
-moz-appearance: none !important;
appearance: none !important;
position: relative;
height: 20px;
width: 100px;
background: transparent;
cursor: pointer;
z-index: 2;
}
input[type="range"]::-moz-range-thumb {
-moz-appearance: none !important;
appearance: none !important;
position: relative;
height: 20px;
width: 100px;
background: transparent;
cursor: pointer;
z-index: 2;
}
.range-text {
position: absolute;
top: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
background: skyblue;
text-align: center;
line-height: 1;
height: 20px;
width: 100px;
transform: translate(0, 2px);
}
<div class="input-container">
<input type="range" min="0" max="100" value="0" step='25'>
<div class="range-text">0</div>
</div>