Grow your CSS skills. Land your dream job.

Value Bubbles for Range Inputs

Published by 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: -webkit-gradient(linear, left top, left bottom, from(#444444), to(#999999));
  background-image: -webkit-linear-gradient(top, #444444, #999999);
  background-image: -moz-linear-gradient(top, #444444, #999999);
  background-image: -ms-linear-gradient(top, #444444, #999999);
  background-image: -o-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.

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

Comments

  1. This is pretty sweet! Thanks Chris!

    How about bubbles that only appear as you slide, then fade out when you release? :P

  2. Michał Czernow
    Permalink to comment#

    This site: http://wufoo.com/html5/types/8-range.html seems to be totally blank at least in new Opera 11.10. Looks like problem with z-index. It’s a bit irony, because Opera is the only browser listed on the site, that fully supports range input :).

    • Uh oh, that’s weird. I can’t reproduce it though. Running 11.10 here and it loads fine.

    • Michał Czernow
      Permalink to comment#

      Oh! you’re right, works perfectly. Though I checked it on Windows 7. I noticed the bug on Windows XP. Hm… I’ll check it again and if something still will be wrong, I’ll let You know.

    • Michał Czernow
      Permalink to comment#

      Ok, bug still exists, but apparently on Windows XP (SP 3). I haven’t checked it on Vista.

      I had to only turn off those declarations:

      left: -3500px;
      opacity: 0;

      for “#content, h1 section” selector and content becomes accessible.

  3. Permalink to comment#

    Chris,

    Great post! You can do some really cool effects with this! I’ll definitely try it myself.

    Thanks

  4. joe
    Permalink to comment#

    Why is it a a bonus that browsers that don’t support the range input still get the bubble action?

    you get the exact same value twice… as long as one doesn’t replace that fallback-inputfield with a slider via javascript the bubble is unnecessary

  5. Brilliant stuff. For one I love how you style the bubble. Secondly it’s relatively simple way to add a lot better usability to the slider element.

  6. Permalink to comment#

    Brilliantly written article. 3 Cheers to Chris.

  7. ion
    Permalink to comment#

    how about IE

  8. Branden
    Permalink to comment#

    On the “Note”, because jQuery’s $().attr() function returns undefined if the attribute does not exist you may do something like this to simplify and shorten the code for that.


    var minVal, maxVal;
    minVal = el.attr("min") || 0;
    maxVal = el.attr("max") || 0;

  9. Permalink to comment#

    I wish I could do this stuff natively, then I wouldn’t have to use 5000 lines of code/libraries just to do these simple sliders: http://www.zirgo.com/

  10. huang47
    Permalink to comment#

    it’s awesome

    I was trying to create several variations by range input, however, the behavior is a bit different on iOS webkit which can not “tap and set value” like the other webkit can do

    http://codepen.io/huang47/full/nDCJi

    Any insights?

  11. Nyemcsok Janos
    Permalink to comment#

    The solution is straight. Replace the range thumb with output :before/after

This comment thread is closed. If you have important information to share, you can always contact me.

*May or may not contain any actual "CSS" or "Tricks".