44

After reading this I was wondering if it is possible to show ticks in Chrome and Firefox for a type="range" number input? The closest thing I could find on this subject is this.

Joel DeWitt
  • 1,226
  • 2
  • 13
  • 26

6 Answers6

63

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.

Woodrow Barlow
  • 8,477
  • 3
  • 48
  • 86
  • [Keep in mind potential compatibility issues](http://caniuse.com/#feat=datalist). – charles Oct 28 '14 at 16:55
  • @charles since it's not a visible element, it will simply be ignored by browsers that don't support it, right? – Woodrow Barlow Oct 28 '14 at 17:53
  • Depends completely on how the browser handles unrecognized tags. A compatibility issue could definitely be the datalist options being visible. I haven't tested this though. – charles Oct 29 '14 at 12:09
  • 1
    @charles that's true, thank you. i suppose if datalist compatibility is a concern, the best approach would be to use the (frustratingly hacky) `repeating-linear-gradient` solution for both webkit and gecko browsers. – Woodrow Barlow Oct 29 '14 at 13:48
  • @charles i've added an aside to address your comments. – Woodrow Barlow Oct 29 '14 at 13:54
  • Does anyone have an example of how one would use `repeating-linear-gradient` to generate custom tick-marks? I'm guessing you would use a `::before` pseudo element and apply the gradient to it, but is there a different approach? – rojobuffalo Feb 06 '15 at 16:55
  • @JonCrowell can you tell me which browsers it fails on and how? i'll update the answer. – Woodrow Barlow Oct 02 '15 at 13:31
  • 7
    i'm a little sad that this answer is still relevant almost three years later. – Woodrow Barlow Sep 01 '17 at 15:08
  • Ion.RangeSlider from JS (What Shiny uses) is cross browser compatible. http://ionden.com/a/plugins/ion.rangeSlider/start.html – Brian Wiley Dec 30 '20 at 03:28
16

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>
vsync
  • 118,978
  • 58
  • 307
  • 400
9

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;
}
user10452457
  • 91
  • 1
  • 1
  • 1
    This is a good solution. To support Safari, I used div instead of datalist and spans instead of options. Note, Bootstrap 4 provides convenient flex classes. – Y. E. Nov 25 '18 at 10:33
9

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: flex;
    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: flex;
  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>

Version History

  • v1.1 - as suggested by @Lien, changed the datalist style from display: block to flex
Ich_73
  • 317
  • 1
  • 4
  • 6
  • 1
    This is the only one that correctly displays labels & ticks on the whole page, in both firefox & chrome. – Steve Horvath Feb 11 '21 at 02:10
  • It doesn't display the labels in the right place in Chrome for me. They're wrapped. – Timmmm Mar 10 '22 at 14:37
  • 1
    @Timmmm Use`display: flex;` to style and it will work. Check out this [codepen](https://codepen.io/movii/pen/wvzrJXB?editors=1100) based on the answer, as of June 2022, it works across Chrome (v102.0.5005.115) and Firefox (v101.0.1). – Lien Jun 12 '22 at 13:02
6

You can use the <datalist> with the range:

<input type="range" min="0" value="0" max="10" step="1" list="ticks">
<datalist id="ticks">
    <option>0</option>
    <option>2</option>
    <option>4</option>
    <option>6</option>
    <option>8</option>
    <option>10</option>
</datalist>
j08691
  • 204,283
  • 31
  • 260
  • 272
6

If custom tick marks are required, pair a slider with a flexbox of custom values. Line up the width of the slider to match the offset of the terminal ticks.

Note: This example only demonstrates the static visuals. JavaScript will be required to translate the slider percentage to custom values, and to highlight the selected tick.

:root {
  font-family: system-ui, -apple-system, -apple-system-font, 'Segoe UI', 'Roboto', sans-serif;
}

h1 {
  font-size: 1.1em;
  text-transform: uppercase;
  color: darkgray;
  letter-spacing: .2em;
}

.options {
  width: 30em;
  border: 1px solid silver;
  border-radius: 1em;
  background-color: ghostwhite;
  padding: 0 0 1em 0;
  text-align: center;
  display: flex;
  flex-direction: column;
}

.ticks {
  display: flex;
}

.o_txt {
  flex: 1;
}

.slider {
  width: 87%; /* manually adjust this to look right */
  margin: auto;
  cursor: grab;
}

.o_on {
  font-weight: 500;
  color: blue;
}
<div class="options">
  <h1>Budget</h1>
  <input class="slider" type="range" min="0" max="100" step="10" list="steplist" value="40" />
  <datalist id="steplist">
     <option>0</option>
     <option>10</option>
     <option>20</option>
     <option>30</option>
     <option>40</option>
     <option>50</option>
     <option>60</option>
     <option>70</option>
     <option>80</option>
     <option>90</option>
     <option>100</option>
  </datalist>
  <div class="ticks">
    <span class="o_txt">$100</span>
    <span class="o_txt">$1K</span>
    <span class="o_txt o_on">$10K</span>
    <span class="o_txt">$100K</span>
    <span class="o_txt">$1M</span>
    <span class="o_txt">$10M</span>
  </div>
</div>
OXiGEN
  • 2,041
  • 25
  • 19