Value Bubbles for Range Inputs

Chris Coyier //

Range inputs in HTML5 are like this:

<input type="range" name="quantity" min="1" max="10">

In browsers that support them, they look like this:

Now that's great and all. You could use it for anything where you want to collect a number from a user that has an enforced minimum and maximum value.

But notice anything weird? All by itself, that range input doesn't communicate to the user what number they will actually be submitting. Now if your input is something like "How are you feeling? Left for sad, right for happy." - then fine, you probably don't need to show the user a number. But I would wager it's more common that you'll need to show the number than not show it.

To be fair, the spec says:

The input element represents a control for setting the element's value to a string representing a number, but with the caveat that the exact value is not important, letting UAs provide a simpler interface than they do for the Number state.

But c'mon, just because we want a cool slider doesn't automatically mean we should prevent the user from knowing the submitted value. I wouldn't necessarily say browsers should alter their UI control to show that number. I am saying we should build that ourselves!

This is the perfect use case for the <output> tag, which is specifically for values calculated by form elements. Here is a super simple implementation of how you might use it:

<input type="range" name="foo">
<output for="foo" onforminput="value = foo.valueAsNumber;"></output>

But let's ixnay that grody inline JavaScript, pull in our friend jQuery, get our CSS on and do this thing right. This goal is below. Any range input, any time, any min/max/step - we put a bubble above it showing the current value.

Let's style the output element first. We'll absolutely position it above the input. That gives us the ability to adjust the left value as well, once we figure out with JavaScript what it should be. We'll fancy it up with gradients and border radius, and even add a little pointer triangle with a pseudo element.

output { 
  position: absolute;
  background-image: linear-gradient(top, #444444, #999999);
  width: 40px; 
  height: 30px; 
  text-align: center; 
  color: white; 
  border-radius: 10px; 
  display: inline-block; 
  font: bold 15px/30px Georgia;
  bottom: 175%;
  left: 0;
  margin-left: -1%;
}
output:after { 
  content: "";
  position: absolute;
  width: 0;
  height: 0;
  border-top: 10px solid #999999;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  top: 100%;
  left: 50%;
  margin-left: -5px;
  margin-top: -1px;
}

Now what we need to do is watch all range inputs for a change in their value. Our goal is to shift the left position of the bubble in pace with the slider. That's not the simplest thing in the world, being that sliders can be of any width and any minimum or maximum value. We're going to have to do a little math. Here's all the jQuery JavaScript, commented up:

// DOM Ready
$(function() {
 var el, newPoint, newPlace, offset;
 
 // Select all range inputs, watch for change
 $("input[type='range']").change(function() {
 
   // Cache this for efficiency
   el = $(this);
   
   // Measure width of range input
   width = el.width();
   
   // Figure out placement percentage between left and right of input
   newPoint = (el.val() - el.attr("min")) / (el.attr("max") - el.attr("min"));
   
   // Janky value to get pointer to line up better
   offset = -1.3;
   
   // Prevent bubble from going beyond left or right (unsupported browsers)
   if (newPoint < 0) { newPlace = 0; }
   else if (newPoint > 1) { newPlace = width; }
   else { newPlace = width * newPoint + offset; offset -= newPoint; }
   
   // Move bubble
   el
     .next("output")
     .css({
       left: newPlace,
       marginLeft: offset + "%"
     })
     .text(el.val());
 })
 // Fake a change to position bubble at page load
 .trigger('change');
});

The one gross part in there is that 1.3 value. I was trying to line up the tip of the bubble's triangle with the center of the slider. It's not easy, because the slider's center is never 100% left or right. That value isn't perfect, nor perfectly implemented, but it's better that not having it.

As a bonus, browsers that don't support the range input still get the bubble action.

NOTE: The above code depends on the range inputs have specified min and max values. If they don't it kinda breaks. I think it would be weird to use a range inputs without specifying these things, although if you don't it seems they default to 0 and 100. To bullet proof this, you'd grab each attribute, test it, and if it didn't look right fix it. Something like:

var minValue, maxValue;
if (!el.attr("min")) { minValue = 0; } else { minValue = el.attr("min"); }

...then use the minValue variable in the math. And do similar for max. Anyway, here's the live demo:

View Demo   Download Files

Improvements welcome.

Update

Here's the above demo on CodePen for your playing pleasure. Another update: I know it's a little janky. It's been a while and some browser stuff must have happened. I'm going to link up some more demos below so hopefully you can find one you like.

See the Pen Range Input Value Bubbles by Chris Coyier (@chriscoyier) on CodePen

Dave Olsen ported the idea to not have a dependency on jQuery. Here's that version:

See the Pen Range Input Value Bubbles (jQuery Free) by Chris Coyier (@chriscoyier) on CodePen

And more!

See the Pen CSS Range Slider by Sean Stopnik (@thelifemgmt) on CodePen.

See the Pen Number Slider by simurai (@simurai) on CodePen.

See the Pen 3D html5 Range price slider by One div (@onediv) on CodePen.

See the Pen Tooltips for Range Inputs by Jeffrey Shaver (@jeffshaver) on CodePen.

And don't forget range input can have datalists which put little notches on them which is kinda cool.