The Input Elements That Won’t Grow Up

Form controls are stubborn things. A plain <input> or <textarea> will sit at its CSS-defined size no matter what the user types into it. There’s no native HTML or CSS mechanism to make them expand with their content—a gap that feels surprising given how natural that behavior is elsewhere on the web.

The Easy Route: Non-Input Elements

Any element can be turned into a field with the contenteditable attribute. A <span> with that attribute will widen to fit its text, and a block-level element like a <div> will grow vertically as needed. That gives you auto-resizing for free, directly from the browser’s own layout engine.

If you go that way, you’ll want to mark it up for assistive tech:

<span 
  class="input" 
  role="textbox" 
  contenteditable>
    99
</span>

The Accessibility Fine Print

But swapping in a contenteditable element is a trade, not a win. Set role="textbox" on it and you’ve made a best guess at accessibility, but a handful of questions remain that aren’t easy to answer with documentation alone:

  • Forms normally submit when the user presses Enter inside a field. Does that still work?
  • Most form-serialization code looks for named controls, not arbitrary spans. What happens to this value on submit?
  • Does a screen reader actually treat a role="textbox" span the same as a true <input>?
  • What other native behaviors do real inputs have that aren’t obvious until they’re gone?

There’s also the matter of accessible naming, voice-control compatibility, and behavior in High Contrast Mode—all things a contenteditable element doesn’t inherit for free. The appeal of getting auto-resizing from the browser is real, but it comes with unknown usability and accessibility risk.

Sticking With Real Form Controls

If you’d rather keep actual <input> and <textarea> elements, JavaScript has to do the heavy lifting.

For a single-line <input>, one approach is to wrap it in a relatively positioned inline parent and absolutely position the field inside it. A hidden <span> in that wrapper mirrors the input’s value, and syncing the two with JavaScript stretches the wrapper—and therefore the visible input—to the right width.

Textareas are trickier. A classic trick counts the line breaks, multiplies by the line-height, and sets the height accordingly. That works well for preformatted content like code, but poorly for paragraph-like prose that wraps at unpredictable points.

A more elegant option comes from a CSS grid trick: JavaScript copies the input’s value into a data-* attribute. A pseudo-element positioned within the same grid uses that attribute as its content, and that expanded pseudo-element is what stretches the grid—and the input—to fit the current text.