CSS field-sizing — Auto-Resize Form Elements with One Line of CSS
# FrontendCSS 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:
- It triggers reflow on every input. Setting
autoand then setting the height back forces the browser to recalculate layout twice. In some cases, the screen will visibly jump - 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
- It’s easy to run into issues when integrating with frameworks. In React, you need
refplususeEffect; in Vue, you may neednextTick. 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-heightbut notmax-height, the height grows with the content - When the content exceeds
max-height, the height stays fixed atmax-heightand 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
- Make Your Hyperlink Underlines Look Better: text-underline-offsetBy default, underlines sit very close to the text, and some designers dislike this style. Personally, I don’t think it looks very good either.
- Why Web Design Shouldn’t Chase Pixel PerfectOnly pay attention to Pixel Perfect when it really matters; otherwise, it often leads to a lose-lose situation.
- Let’s Write Colors with CSS HSL! (And a Better Way)In web development, the traditional HEX and RGB color notations are widely used, but they are not very readable or intuitive, and their capabilities are limited in wider color spaces such as P3. HSL (Hue, Saturation, Lightness) provides a more intuitive way to define colors, making it easier for developers to understand and adjust them. By describing colors through the three dimensions of hue, saturation, and lightness, HSL makes color adjustment more human-friendly. In design systems in particular, HSL can better represent lightness variations in a color palette.
- The reasons to avoid setting line-height to 1 and using ellipsis whenever possibleThis article discusses why it's not advisable to set the line-height to 1 in web design, as well as the linguistic issues encountered when using ellipsis.