Ticks for type="range" HTML input
Input ranges are still a bit of a nightmarish hack when it comes to styling. That said, displaying tickmarks on major browsers is possible, with a bit of elbow grease and browser-specific solutions.
Internet Explorer / Edge
As you seem to be aware, Internet Explorer will show ticks by default if you add the HTML step
attribute. In a weird twist of events, Internet Explorer and Edge are arguably the most flexible browser when it comes to styling input ranges.
<input type="range" min="0" max="100" step="25">
Chrome / Safari
In Chrome and other Webkit browsers (including Safari), you can use the datalist element to provide a custom set of tick locations on the slider. While all major browsers support this element, Firefox (and other Gecko browsers) won't show visible tick marks.
<input type="range" min="0" max="100" step="25" list="steplist">
<datalist id="steplist">
<option>0</option>
<option>25</option>
<option>50</option>
<option>75</option>
<option>100</option>
</datalist>
Firefox
In Firefox and other Gecko-based browsers, we'll need to use some vendor-specific CSS to add the tick marks. You'll have to customize this to whatever looks the most natural to you. In this example, I've used a horizontal repeating gradient to add "vertical stripes" that look like tick marks, but you could also use a background image, or any other style you want. You could even use a bit of Javascript to load information from the datalist element, then generate an appropriate gradient and apply it to the element so that it all happens automatically, and so it can support custom arbitrary stops.
input[type="range"]::-moz-range-track {
padding: 0 10px;
background: repeating-linear-gradient(to right,
#ccc,
#ccc 10%,
#000 10%,
#000 11%,
#ccc 11%,
#ccc 20%);
}
<input type="range" min="0" max="100" step="25" list="steplist">
<datalist id="steplist">
<option>0</option>
<option>25</option>
<option>50</option>
<option>75</option>
<option>100</option>
</datalist>
Compatibility notes: As pointed out in the comments, datalist is not supported by some browsers. Depending on how those browsers handle unsupported / unrecognized elements, this may result in the browsers displaying the option values as plain text below your range input. If targeting the widest possible range of browsers is important to you, this may be a problem.
One solution is to use the awkward repeating-linear-gradient
kludge for webkit browsers in addition to gecko browsers, and then remove the datalist entirely.
Another solution would be to use CSS to explicitly set the datalist to display: none
. This solution is probably the most preferable, as you aren't compromising features to provide legacy support.
I hope, this will help somebody formating the tick and datalist under FF. Requires the options to be evenly spaced and input + datalist have to have the same width.
input[type="range"] {
width: 100%;
margin: 0;
box-sizing: border-box;
}
datalist {
display: flex;
width: 100%;
justify-content: space-between;
margin-top: -23px;
padding-top: 0px;
}
option {
width: 2ex;
display: flex;
justify-content: center;
height: 42px;
align-items: end;
background-image: url(tick.png);
height: 4ex;
background-position-y: -15px;
background-position-x: center;
z-index: -1;
}
Range input with CSS-only ticks
I have developed my own lightweight component which renders ticks using only CSS, with linear-gradient
background
property, by using CSS variables (AKA "custom properties").
Unfortunately, a wrapper element is needed, but it's the most minimal HTML modifications I came up with which allows the outcome.
Manual-syncing between the range input's attributes and the wrapper element's style
attribute which hosts the variables is also unfortunately needed.
Fancy Codepen Demo
body {
height: 100vh;
display: grid;
place-items: center;
}
.range {
--ticksThickness: 2px;
--ticksHeight: 30%;
--ticksColor: silver;
display: inline-block;
background: silver;
background: linear-gradient(to right, var(--ticksColor) var(--ticksThickness), transparent 1px) repeat-x;
background-size: calc(100%/((var(--max) - var(--min)) / var(--step)) - .1%) var(--ticksHeight);
background-position: 0 bottom;
position: relative;
}
/* min / max labels at the edges */
.range::before, .range::after {
font: 12px monospace;
content: counter(x);
position: absolute;
bottom: -2ch;
}
.range::before {
counter-reset: x var(--min);
transform: translateX(-50%);
}
.range::after {
counter-reset: x var(--max);
right: 0;
transform: translateX(50%);
}
.range > input {
width: 300px;
margin: 0 -6px; /* Critical adjustment */
}
<div class="range" style="--step:10; --min:20; --max:100">
<input type="range" min="20" max="100" step="10" value="30">
</div>
Introduction
Inspired by user10452457's answer I created an approach that corrects the spacing of datalist elements especially when the length of datalist entries varies. The downside is that you have to specify the length of the datalist in its style
attribute: <datalist style="--list-length: XYZ;">...</datalist>
. If the length is not known when creating the datalist you can change this value by using some javascript.
This approach works by splitting the width of the range input in such a way that the text of the datalist entry is centered below the range thumb. The first/last datalist entry is aligned to the left/right. This method requires the width
of the range input to be 100%
and the margin-left
to be 0
.
Default range and thumb
The following css styles a datalist that directly follows a range input when using the default thumb of Firefox that has a width of 12px
.
/* style range */
input[type=range] {
width: 100%;
max-width: 100%;
margin-left: 0;
}
/* style datalist */
input[type=range] + datalist {
display: block;
margin-top: -4px;
}
input[type=range] + datalist option {
display: inline-block;
width: calc((100% - 12px) / (var(--list-length) - 1));
text-align: center;
}
input[type=range] + datalist option:first-child {
width: calc((100% - 12px) / ((var(--list-length) - 1) * 2) + 6px);
text-align: left;
}
input[type=range] + datalist option:last-child {
width: calc((100% - 12px) / ((var(--list-length) - 1) * 2) + 6px);
text-align: right;
}
<input type="range" min="1" max="9" id="my-range" list="my-datalist"/>
<datalist id="my-datalist" style="--list-length: 9;"><!--
---><option>1</option><!--
---><option>2</option><!--
---><option>3</option><!--
---><option>A</option><!--
---><option>B</option><!--
---><option>C</option><!--
---><option>Four</option><!--
---><option>Five</option><!--
---><option>Six</option><!--
---></datalist>
Custom range and thumb
When you want to use a custom style for your range input you can use the following style. The --thumb-width
variable holds the with of the thumb and is used for the correct calculation.
// change thumb-width variable on input change
var tw = document.getElementById('thumb-width');
var mr = document.getElementById('my-range');
var ml = document.getElementById('my-datalist');
tw.onchange = () => {
mr.style.setProperty('--thumb-width', tw.value + 'px');
ml.style.setProperty('--thumb-width', tw.value + 'px');
}
/* set thumb width */
input[type=range], input[type=range] + datalist { --thumb-width: 8px; }
/* style range */
input[type=range] {
-webkit-appearance: none; /* hide track and thumb */
width: 100%;
max-width: 100%;
margin-left: 0;
}
/* style datalist */
input[type=range] + datalist {
display: block;
margin-top: -4px;
}
input[type=range] + datalist option {
display: inline-block;
width: calc((100% - var(--thumb-width)) / (var(--list-length) - 1));
text-align: center;
}
input[type=range] + datalist option:first-child {
width: calc((100% - var(--thumb-width)) / ((var(--list-length) - 1) * 2) + var(--thumb-width) / 2);
text-align: left;
}
input[type=range] + datalist option:last-child {
width: calc((100% - var(--thumb-width)) / ((var(--list-length) - 1) * 2) + var(--thumb-width) / 2);
text-align: right;
}
/* style Firefox range and thumb */
input[type=range]::-moz-range-track {
background: #eee;
cursor: pointer;
height: 2px;
border: 1px solid #888;
border-radius: 1px;
}
input[type=range]::-moz-range-thumb {
background: #eee;
box-sizing: border-box;
width: var(--thumb-width);
height: 20px;
cursor: pointer;
border: 1px solid #888;
border-radius: 3px;
}
/* style Chrome range and thumb */
input[type=range]::-webkit-slider-runnable-track {
background: #eee;
cursor: pointer;
height: 2px;
border: 1px solid #888;
border-radius: 1px;
}
input[type=range]::-webkit-slider-thumb {
background: #eee;
box-sizing: border-box;
width: var(--thumb-width);
height: 20px;
cursor: pointer;
border: 1px solid #888;
}
<label>Thumb width:</label>
<input type="number" id="thumb-width" min="4" max="60" step="4" value="10"/>
<br><br>
<input type="range" min="1" max="9" id="my-range" list="my-datalist"/>
<datalist id="my-datalist" style="--list-length: 9;"><!--
---><option>1</option><!--
---><option>2</option><!--
---><option>3</option><!--
---><option>A</option><!--
---><option>B</option><!--
---><option>C</option><!--
---><option>Four</option><!--
---><option>Five</option><!--
---><option>Six</option><!--
---></datalist>