Adjust width of input field to its input

Viewed 435951

<input type="text" value="1" style="min-width:1px;" />

This is my code and it is not working. Is there any other way in HTML, JavaScript, PHP or CSS to set minimum width?

I want a text input field with a dynamically changing width, so that the input field fluids around its contents. Every input has a built-in padding of 2em, that is the problem and second problem is that min-width ain't working on input at all.

If I set width more than it is needed than the whole program is messy, I need the width of 1px, more only if it's needed.

32 Answers

This answer provides one of the most accurate methods of retrieving text width available in the browser and is more accurate than the accepted answer. It uses the canvas html5 element and unlike other answers does not add the element into the DOM and thus avoids any reflow issues caused by excessively adding elements to the DOM.

Read more about the Canvas element here in relation to text width.

NOTE: According to MDN the shorthand versions of the getPropertyValue() method such as font can be unreliable. I'd recommend getting the values singularly to improve compatibility. I only used it here for speed.

/**
 * returns the width of child text of any DOM node as a float
 */
function getTextWidth(el) {
  // uses a cached canvas if available
  var canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
  var context = canvas.getContext("2d");
  // get the full font style property
  var font = window.getComputedStyle(el, null).getPropertyValue('font');
  var text = el.value;
  // set the font attr for the canvas text
  context.font = font;
  var textMeasurement = context.measureText(text);
  return textMeasurement.width;
}

var input = document.getElementById('myInput');
// listen for any input on the input field
input.addEventListener('input', function(e) {
  var width = Math.floor(getTextWidth(e.target));
  // add 10 px to pad the input.
  var widthInPx = (width + 10) + "px";
  e.target.style.width = widthInPx;
}, false);
#myInput {
  font: normal normal 400 normal 18px / normal Roboto, sans-serif;
  min-width: 40px;
}
<input id="myInput" />

You can just set size attribute. If you're using one of reactive frameworks, the following will be enough:

<input size="{{ yourValue.length }}" [value]="yourValue" />

but if you're using pure js, you should set event handlers, like:

<input oninput="this.setAttribute('size', this.value.length)" />

It's worth noting that a nice-looking resize can be done when the font is monospaced, so we can perfectly resize the input element using the ch unit.

Also in this approach we can update the width of the field by just updating a CSS variable (custom property) on input event and we should also take care of already pre-filled input on DOMContentLoaded event

Codepen demo


Markup

<input type="text" value="space mono font" class="selfadapt" />

CSS

:root { --size: 0; }

.selfadapt {
   padding: 5px;
   min-width: 10ch;
   font-family: "space mono";
   font-size: 1.5rem;
   width: calc(var(--size) * 1ch);
}

As a root variable we set --size: 0: this variable will contain the length of the input and it will be multiplied by 1ch inside the calc() expression. By default we could also set a min-width, e.g. 10ch

The Javascript part reads the length of the value inserted and updates the variable --size:

JS

let input = document.querySelector('.selfadapt');
let root  = document.documentElement.style;

/* on input event auto resize the field */
input.addEventListener('input', function() {
   root.setProperty('--size', this.value.length );
});

/* resize the field if it is pre-populated */
document.addEventListener("DOMContentLoaded", function() {
   root.setProperty('--size', input.value.length);
});

of course this still works even if you don't use monospaced font, but in that case you will need to change the calc() formula by multiplying the --size variable by another value (which it's strictly dependent on the font-family and font-size) different than 1ch.

Quite simple:

oninput='this.style.width = (this.scrollWidth - N) + "px";'

Where N is some number (2 in the example, 17 on something I'm developing) that is determined experimentally.

Subtracting N prevents this strange extrenuous space from accumulating long before the text reaches the end of the text box.

Compare. Pay careful attention to how the size changes after even just the first character.

<p>Subtracting N:</p>    
<input type="text" placeholder="enter lots of text here" oninput='this.style.width = (this.scrollWidth-2) + "px";'>

<p>Not Subtracting N:</p>    
<input type="text" placeholder="enter lots of text here" oninput='this.style.width = (this.scrollWidth) + "px";'>

If you use Bootstrap, it could be done very easily:

<div contenteditable="true" class="form-control" style="display: inline"></div>

You will just need to fetch div's content and put it in a hidden input before submitting the form.

Here is my 2 cents. Create an empty invisible div. Fill it with the input content and return the width to the input field. Match text styles between each box.

$(".answers_number").keyup(function(){
    $( "#number_box" ).html( $( this ).val() );
    $( this ).animate({
        width: $( "#number_box" ).width()+20
        }, 300, function() {
    });
});
#number_box {
   position: absolute;
   visibility: hidden;
   height: auto;
   width: auto;
   white-space: nowrap;
   padding:0 4px;
   /*Your font styles to match input*/
   font-family:Arial;
   font-size: 30px; 
}
    
.answers_number {
   font-size: 30px; 
   font-family:Arial;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="number" class="answers_number" />
<div id="number_box">
</div>

Here's a simple function to get what is needed:

function resizeInput() {
    const input = document.getElementById('myInput');
    input.style.width = `${input.scrollWidth}px`;
};

The best solution is <input ... size={input.value.length} />

Here is my React solution, it works with any font size, just make sure you have a monospace font (all font character widths are the same on monospace fonts) like i have in my solution, and it will work perfectly.

JS:

const [value, setValue] = useState(0)

HTML:

<input value={value} onChange={(e) => {setValue(e.target.value)}} style={{width: `${value.toString().length}`ch}}/>

CSS:

@import url("https://fonts.googleapis.com/css2?family=B612+Mono&display=swap");    
input{
    font-family: "B612 Mono", monospace;
}

Svelte version:

<input type="text" style="width: {tag.length}ch" bind:value={tag} />

A bullet-proof, generic way has to:

  1. Take into account all possible styles of the measured input element
  2. Be able to apply the measurement on any input without modifying the HTML or

Codepen demo

var getInputValueWidth = (function(){
  // https://stackoverflow.com/a/49982135/104380
  function copyNodeStyle(sourceNode, targetNode) {
    var computedStyle = window.getComputedStyle(sourceNode);
    Array.from(computedStyle).forEach(key => targetNode.style.setProperty(key, computedStyle.getPropertyValue(key), computedStyle.getPropertyPriority(key)))
  }
  
  function createInputMeassureElm( inputelm ){
    // create a dummy input element for measurements
    var meassureElm = document.createElement('span');
    // copy the read input's styles to the dummy input
    copyNodeStyle(inputelm, meassureElm);
    
    // set hard-coded styles needed for propper meassuring 
    meassureElm.style.width = 'auto';
    meassureElm.style.position = 'absolute';
    meassureElm.style.left = '-9999px';
    meassureElm.style.top = '-9999px';
    meassureElm.style.whiteSpace = 'pre';
    
    meassureElm.textContent = inputelm.value || '';
    
    // add the meassure element to the body
    document.body.appendChild(meassureElm);
    
    return meassureElm;
  }
  
  return function(){
    return createInputMeassureElm(this).offsetWidth;
  }
})();


// delegated event binding
document.body.addEventListener('input', onInputDelegate)

function onInputDelegate(e){
  if( e.target.classList.contains('autoSize') )
    e.target.style.width = getInputValueWidth.call(e.target) + 'px';
}
input{ 
  font-size:1.3em; 
  padding:5px; 
  margin-bottom: 1em;
}

input.type2{
  font-size: 2.5em;
  letter-spacing: 4px;
  font-style: italic;
}
<input class='autoSize' value="type something">
<br>
<input class='autoSize type2' value="here too">

You would like to change the size attribute as the text changes.

# react

const resizeInput = (e) => {
  e.target.setAttribute('size', e.target.value.length || 1);
}

<input 
  onChange={resizeInput}
  size={(propertyInput.current && propertyInput.current.value.length) || 1}
  ref={propertyInput} 
/>

  

Update for answer https://stackoverflow.com/a/41389961/1022684

If you have multiple inputs and do not want to repeat input text in spans:

  $(() => {
    $('.container input').on('input', (e) => {
      $(e.target).prev().text($(e.target).val())
    })  
    $('.container input').each((idx, elem) => {
      $(elem).prev().text($(elem).val())
    })
  })

Why not using just css?

<div id="wrapper">
  <input onkeyup="keyup(event)">
  <div id="ghost"></div>
</div>

function keyup(e) {
 document.getElementById('ghost').innerText = e.target.value;
}
#wrapper {
  position: relative;
  min-width: 30px;
  display: inline-block;
}

input {
  position: absolute;
  left:0;
  right:0;
  border:1px solid blue;
  width: 100%;
}

#ghost {
  color: transparent;
}
<div id="wrapper">
  <input onkeyup="keyup(event)">
  <div id="ghost"></div>
</div>

wrapper {
  position: relative;
  min-width: 30px;
  border: 1px solid red;
  display: inline-block;
}

input {
  position: absolute;
  left:0;
  right:0;
  width: 100%;
}

#ghost {
  color: transparent;
}

this code was introduced by @Iain Todd to and I thought I should share it

Related