36

I currently have a number of inputs like this:

<input type="number" id="milliseconds">

This input field is used to represent a value in milliseconds. I do however have multiple number inputs which take a value in dB or percentages.

<input type="number" id="decibel">
<input type="number" id="percentages">

What I would like to do is add a type suffix to the input field to let users know what kind of value the input represents. Something like this:

enter image description here

(This image is edited to show what result I want to have,I hid the up and down arrows from the input type as well).

I have tried to Google this but I can't seem to find anything about it. Does anyone know if this is possible, and how you can accomplish something like this?

andreas
  • 16,357
  • 12
  • 72
  • 76
R. De Caluwe
  • 427
  • 1
  • 4
  • 13
  • 2
    placing the unit just aside of the input (as a text node) is not an option? – Fabrizio Calderan Apr 12 '18 at 12:47
  • 1
    Try `placeholder` https://www.w3schools.com/tags/att_input_placeholder.asp – Adam Apr 12 '18 at 12:47
  • That would be possible but I would have to redesign some parts of my UI which I would like to avoid as much as possible :) . But if it's impossible to do something like this I would have no other choice. – R. De Caluwe Apr 12 '18 at 12:49
  • @Adam I looked at this and even though it shows text when the value is empty, I still would need that suffix after changing the value which placeholder doesn't do. Thanks for your suggestion though! – R. De Caluwe Apr 12 '18 at 12:50
  • 1
    As you have id specific to each kind of units, there is a lot more chances that a Javascript function would do the trick. I think you could use [number_format js function](https://stackoverflow.com/a/34141813/4627117) with the add of a 5th parameter "unit". If you don't want to use Javascript, then would a PHP function suits you ? – fictimaph Apr 12 '18 at 13:00
  • @fictimaph Thanks for your suggestion! I do use Javascript in my application for fetching values and other types of data from my input fields. Even though your suggestion is a good one, I would only like to know if I could add a suffix to a number input field. Your suggestion would make my application a little too complicated for such a 'basic' feature. I would be better of redesigning my UI a little :) Thanks anyway! – R. De Caluwe Apr 12 '18 at 13:06
  • You're welcome ! A Javascript function replacing each "number" input, given the id was possible, calling it once the page loaded. Then it can be used to show datas and inputs. But indeed the CSS solution is just perfect. – fictimaph Apr 12 '18 at 13:17

4 Answers4

52

You can use a wrapper <div> for each input element and position the unit as a pseudo element ::after with the content of your corresponding units.

This approach works well for the absolute positioned pseudo elements will not effect the existing layouts. Nevertheless, the downside of this approach is, that you have to make sure, that the user input is not as long as the text field, otherwise the unit will be unpleasantly shown above. For a fixed user input length, it should work fine.

/* prepare wrapper element */
div {
  display: inline-block;
  position: relative;
}

/* position the unit to the right of the wrapper */
div::after {
  position: absolute;
  top: 2px;
  right: .5em;
  transition: all .05s ease-in-out;
}

/* move unit more to the left on hover or focus within
   for arrow buttons will appear to the right of number inputs */
div:hover::after,
div:focus-within::after {
  right: 1.5em;
}

/* handle Firefox (arrows always shown) */
@supports (-moz-appearance:none) {
  div::after {
    right: 1.5em;
  }
}

/* set the unit abbreviation for each unit class */
.ms::after {
  content: 'ms';
}
.db::after {
  content: 'db';
}
.percent::after {
  content: '%';
}
<div class="ms">
  <input type="number" id="milliseconds" />
</div>
<hr />
<div class="db">
  <input type="number" id="decibel" />
</div>
<hr />
<div class="percent">
  <input type="number" id="percentages">
</div>

If you want to support browsers, that doesn't show these arrows at all, make use of @supports or media queries.

andreas
  • 16,357
  • 12
  • 72
  • 76
  • 1
    Oh wow, I actually never thought about using a CSS solution for this even though I just realised I used the CSS tag in my question :) But your solution would work great for this problem since the user input would never exceed a prefixed amount of characters ( 2 or 3 at most ). Thank you very much for your suggestion, will mark this as answer! :) – R. De Caluwe Apr 12 '18 at 13:08
  • Well I'm glad I could help! – andreas Apr 12 '18 at 13:10
  • 1
    There is a `content` option in CSS, that's great, didn't know ! – fictimaph Apr 12 '18 at 13:14
  • Added a link to MDN for the pseudo element `::after`. For those elements, you can use the `content` rule. – andreas Apr 12 '18 at 13:17
  • You could use padding to prevent the input's text from overlapping with the suffix label. Or, you can use a container styled as input box (with border and such) and an input element inside of it without borders. – Slava Fomin II Oct 30 '18 at 07:59
  • 2
    Instead of only using `:hover::after` also add `:focus-within::after` – N7D Dec 03 '19 at 12:52
  • @N7d Thank you for this addition! I updated my answer accordingly. – andreas Dec 03 '19 at 16:09
  • 1
    This works great in Chrome and Edge on desktop computers, but: 1. Firefox on desktop computers always shows the up and down buttons even when the input isn't selected, so you should add `@supports ( -moz-appearance:none ){div::after {right: 1.5em;}}` ([`@supports ( -moz-appearance:none )` detects Firefox](https://stackoverflow.com/a/45639929/4284627)), and 2. Mobile browsers never show the up and down buttons at all, so you should surround `div:hover::after, div:focus-within::after` by `@media(min-device-width:481px)`. – Donald Duck Jul 03 '20 at 17:40
  • Thank you @DonaldDuck for this valuable addition, I updated my answer accordingly. – andreas Jul 05 '20 at 12:21
29

Another interesting approach would be to use a little of JavaScript in order to make suffix actually stick to the input text (which probably looks better):

const inputElement = document.getElementById('my-input');
const suffixElement = document.getElementById('my-suffix');


inputElement.addEventListener('input', updateSuffix);

updateSuffix();

function updateSuffix() {
  const width = getTextWidth(inputElement.value, '12px arial');
  suffixElement.style.left = width + 'px';
}


/**
 * Uses canvas.measureText to compute and return the width of the given text of given font in pixels.
 * 
 * @param {String} text The text to be rendered.
 * @param {String} font The css font descriptor that text is to be rendered with (e.g. "bold 14px verdana").
 * 
 * @see https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393
 */
function getTextWidth(text, font) {
    // re-use canvas object for better performance
    var canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
    var context = canvas.getContext("2d");
    context.font = font;
    var metrics = context.measureText(text);
    return metrics.width;
}
#my-input-container {
  display: inline-block;
  position: relative;
  font: 12px arial;
}

#my-input {
  font: inherit;
}

#my-suffix {
  position: absolute;
  left: 0;
  top: 3px;
  color: #555;
  padding-left: 5px;
  font: inherit;
}
<div id="my-input-container">
  <input type="number" id="my-input" value="1500">
  <span id="my-suffix">ms.</span>
</div>

However, this is just a proof of concept. You will need to work on it a little further to make it production-ready, e.g. make it a reusable plugin.

Also, you will need to handle a case, where input element is getting overflowed.

Slava Fomin II
  • 26,865
  • 29
  • 124
  • 202
4

If you have option to add elements to input then you can try this:

.container {
  max-width: 208px;    /*adjust it*/
  margin: auto;
  position: relative;
  display: inline-block;
}

#milliseconds {
  padding-right: 35px;
}

.ms {
  position: absolute;
  top: 0;
  right: 10px;
}
<div class="container">
  <input type="text" id="milliseconds">
  <span class="ms">ms</span>
</div>
Donald Duck
  • 8,409
  • 22
  • 75
  • 99
Akanksha Sharma
  • 271
  • 1
  • 8
2

I have a case where the design team wants the suffix to float with the values. We are using a custom font with very uneven number widths. I came with an idea to use a ghost to follow the input width and clamp the overflow with max-width by using a wrapper element. This is still a bit work in progress and glitchy (no initial fill, etc.).

const fillBuffer = (e) => {
  // Clear the buffer if input gets wiped
  if (e.target.value.length === 0) {
    e.target.parentElement.querySelector('.suffix span').textContent = "";
    return;
  }

  // Using a filler char will prevent the suffix to be overwritten with the input
  const extraFiller = e.target.value.length ? '1' : '';

  e.target.parentElement.querySelector('.suffix span').textContent = e.target.value + extraFiller;
}

// Attach the listeners
document.querySelectorAll('input').forEach((el) => {
  el.addEventListener('keydown', fillBuffer);
  el.addEventListener('keyup', fillBuffer);
});
* {
  font-size: 1em;
  font-family: Papyrus, sans-serif;
  box-sizing: border-box;
}

body {
  padding: 1em;
}

.input-wrapper {
  margin-bottom: 0.5em;
}

.input-wrapper.with-suffix {
  position: relative;
  max-width: 150px;
  overflow: hidden;
}

.input-wrapper.with-suffix input {
  margin: 0;
  width: 100%;
  outline: 0;
  padding-left: 4px;
  padding-right: 16px;
}

.input-wrapper.with-suffix .suffix {
  position: absolute;
  padding-left: 6px;
  top: 2px;
  pointer-events: none;
  width: 100%;
}

.input-wrapper.with-suffix .suffix span {
  user-select: none;
  pointer-events: none;
}

.input-wrapper.with-suffix .suffix .filler {
  display: inline-block;
  white-space: pre; /* Allow more than two whitespaces to be rendered */
  color: rgba(0, 0, 0, 0.0);
  background: rgba(255, 0, 0, 0.3);
  max-width: calc(100% - 16px);
}
<div class="input-wrapper with-suffix">
  <input type="text" value="5000">
  <div class="suffix"><span class="filler">5000</span><span>€</span></div>
</div>
  
  
<div class="input-wrapper with-suffix">
  <input type="text" value="5000">
  <div class="suffix"><span class="filler">5000</span><span>€</span></div>
</div>
sampoh
  • 3,066
  • 1
  • 16
  • 14
  • This looks pretty good as well! However, wouldn't it be easier to use the onchange event listener? That might reduce the buggyness overall, since waiting for the key events might differ from user to user ( or keyboard to keyboard )? – R. De Caluwe Jun 25 '21 at 13:40