· 7 min read

CSS field-sizing — Auto-Resize Form Elements with One Line of CSS

# Frontend
This article was auto-translated from Chinese. Some nuances may be lost in translation.

CSS field-sizing: content lets a textarea auto-resize based on its content with a single line — no JavaScript needed. But before this property existed, it took real effort to get this right.

Listening for the input event with JavaScript

The most common approach is to listen for the input event and dynamically adjust the height using scrollHeight:

const textarea = document.querySelector('textarea')

textarea.addEventListener('input', () => {
  textarea.style.height = 'auto'
  textarea.style.height = `${textarea.scrollHeight}px`
})

The logic here is: first reset the height to auto so the browser recalculates scrollHeight, then set the scrollHeight value back as the height. This approach looks straightforward, but it actually has a few issues:

  1. It triggers reflow on every input. Setting auto and then setting the height back forces the browser to recalculate layout twice. In some cases, the screen will visibly jump
  2. You need to handle the initial state. If the textarea already has content when the page loads (for example, in edit mode), you still need to trigger it manually once
  3. It’s easy to run into issues when integrating with frameworks. In React, you need ref plus useEffect; in Vue, you may need nextTick. Each framework handles it a little differently

If you’re using React, it would look something like this:

function AutoResizeTextarea(props) {
  const ref = useRef<HTMLTextAreaElement>(null)

  useEffect(() => {
    const el = ref.current
    if (!el) return

    const resize = () => {
      el.style.height = 'auto'
      el.style.height = `${el.scrollHeight}px`
    }

    el.addEventListener('input', resize)
    resize() // initialize

    return () => el.removeEventListener('input', resize)
  }, [])

  return <textarea ref={ref} {...props} />
}

For a feature as simple as “adjust height based on content,” you need to build a component, manage a ref, bind events, and handle cleanup. That’s not even counting cases where you want to add a maxHeight limit or support programmatically changing the content.

Creating a shadow textarea

Another approach is to create a Shadow Textarea, meaning there are two <textarea> elements in the DOM. Their styles are kept the same, except the shadow textarea is hidden using visibility: hidden so it doesn’t appear on the screen.

<textarea                          // visible
  rows={minRows}
  style={style}
/>
<textarea                          // hidden shadow
  aria-hidden
  readOnly
  tabIndex={-1}
  style={{ visibility: 'hidden', position: 'absolute', overflow: 'hidden', height: 0 }}
/>

The main purpose is to prevent flicker while the textarea is being updated from height: auto to height: ${scrollHeight}. So the process is roughly:

  • Give the hidden textarea the calculated width of the visible textarea (computedStyle.width)
  • Give the hidden textarea the visible textarea’s value
  • Read the hidden textarea’s scrollHeight
  • Apply the calculated height to the visible textarea

I vaguely remember that someone used a div as the shadow element even earlier, but I can’t find the link anymore. The direction is the same, though: create a hidden element to avoid flickering in the visible textarea.

Here’s the core logic, referencing MUI’s TextareaAutosize:

function syncHeight(textarea, shadow, minRows) {
  const computedStyle = window.getComputedStyle(textarea);
  shadow.style.width = computedStyle.width;
  shadow.value = textarea.value ||x;

  // If the last char is a newline, add a space to avoid inaccurate height
  if (shadow.value.endsWith(‘\n’)) {
    shadow.value += ‘ ‘;
  }

  const contentHeight = shadow.scrollHeight;

  // Single-line height, used to calculate minRows
  shadow.value =x;
  const singleRowHeight = shadow.scrollHeight;

  const outerHeight = Math.max(minRows * singleRowHeight, contentHeight);
  textarea.style.height = `${outerHeight}px`;
}

A full implementation also needs to handle ResizeObserver listeners. MUI’s source has a notable workaround: it pauses ResizeObserver while adjusting the height, then re-observes one frame later to avoid the "ResizeObserver loop completed with undelivered notifications" error. This single edge case shows just how tedious it is to maintain auto-resize in JavaScript.

field-sizing: content

Now CSS natively provides the field-sizing property, and you can do it in just one line:

textarea {
  field-sizing: content;
}

That’s it. The browser will automatically adjust the size of the form element based on its content. If you want a default height, remember to add min-height or max-height. I like using rows to set the height, but once you add field-sizing: content, rows and cols no longer have any effect.

rows and cols attributes modify the default preferred size of a

If you use only field-sizing: content, the element will keep expanding with its content without limit. In practice, you’ll almost always want to pair it with min-width, max-width, min-height, and max-height to control the range:

textarea {
  field-sizing: content;
  min-height: 3lh;
  max-height: 10lh;
  min-width: 200px;
  max-width: 100%;
}

I recommend using lh together with these settings, because the intent becomes much clearer. lh represents the element’s own line-height. 3lh means the height of three lines, which is more semantic than using px or em, and less likely to cause layout issues when font sizes change.

The behavior is like this:

  • When the content is less than min-height, it stays at the minimum height
  • When the content exceeds min-height but not max-height, the height grows with the content
  • When the content exceeds max-height, the height stays fixed at max-height and a scrollbar appears

The width logic works the same way. For example, with <input>:

input {
  field-sizing: content;
  min-width: 100px;
  max-width: 400px;
}

When there’s little text, it stays at 100px; as more text is entered, it gradually widens; once it exceeds 400px, it stops expanding.

Which elements can it be used on?

field-sizing is not only for <textarea>; it also supports <input> and <select>:

/* input will automatically adjust its width based on the length of the input text */
input {
  field-sizing: content;
}

/* select will adjust its width based on the length of the option text */
select {
  field-sizing: content;
}

field-sizing browser support

As of April 2026, field-sizing is already supported in Chrome 123+, Edge 123+, and Opera 109+. Safari has also added support in the latest Technology Preview version. Firefox is still under development.

Overall, support across major browsers is catching up quickly, but if your product needs to support older browsers, you still need to keep a JavaScript fallback for now. What I usually do is add a check to see whether the browser supports it:

const isFieldSizingSupported = CSS.supports('field-sizing', 'content');

Although branching code conditionally is a bit more troublesome, CSS still saves a lot of unnecessary JavaScript. As long as I can add it, I still will.

Conclusion

field-sizing: content solves a problem frontend engineers have dealt with for over a decade. No matter the framework, the essence was always manually synchronizing content height in JavaScript. Now the browser handles it natively — what you save isn’t just lines of code, but the effort of maintaining those workarounds.

It feels a lot like discovering that scroll-behavior: smooth could replace an entire smooth scroll library with a single line. Browsers are gradually bringing these common needs into native support, so developers can focus on what really matters.

Related Posts

Explore Other Topics