CSS field-sizing — Auto-resize Form Elements with a Single Line of CSS
With just a single line, CSS field-sizing: content allows a <textarea> to automatically resize its height based on its content—no JavaScript required. But before this property came along, accomplishing this was no easy task.
Listening to the input Event with JavaScript
The most common approach has been listening to the input event and dynamically adjusting the height via scrollHeight:
const textarea = document.querySelector('textarea')
textarea.addEventListener('input', () => {
textarea.style.height = 'auto'
textarea.style.height = `${textarea.scrollHeight}px`
})
The logic here is straightforward: first reset the height to auto so the browser recalculates scrollHeight, then assign the scrollHeight back to the element’s height. While this approach seems intuitive, it has several issues:
- Every keystroke triggers a reflow. Setting the height to
autoand then setting it back forces the browser to recalculate the layout twice. This can easily cause noticeable layout jitter or flickering. - Initial state must be handled manually. If the textarea already contains content when the page loads (such as in an edit mode), you have to trigger the calculation once by hand.
- Framework integrations are full of pitfalls. In React you need a
refcombined withuseEffect, in Vue you might neednextTick, and every framework handles it slightly differently.
In React, it would look 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} />
}
Just to achieve “resizing based on content,” you have to create a component, manage refs, attach event listeners, and handle cleanup. And that’s not even counting scenarios where you need to enforce a maxHeight limit or support programmatic content updates.
Creating a Shadow Textarea
Another technique is creating a shadow textarea, meaning there are two <textarea> elements in the DOM. Their styles are kept identical, 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 the visual flicker that occurs when toggling textarea between height: auto and height: ${scrollHeight}. The flow goes like this:
- Apply the visible
textarea’s calculated width (computedStyle.width) to the hidden textarea - Pass the visible
textarea’s value to the hidden textarea - Read the hidden
textarea’sscrollHeight - Apply that calculated height to the visible
textarea
I remember people used to use a div as the shadow element even earlier, though I can no longer find the link. The premise was the same: using an off-screen element to prevent the visible textarea from flickering.
Here is the core logic, adapted from MUI’s TextareaAutosize:
function syncHeight(textarea, shadow, minRows) {
const computedStyle = window.getComputedStyle(textarea);
shadow.style.width = computedStyle.width;
shadow.value = textarea.value || 'x';
// textarea 如果最後一個字是換行,補一個空白避免高度計算不準
if (shadow.value.endsWith('\n')) {
shadow.value += ' ';
}
const contentHeight = shadow.scrollHeight;
// 單行高度,用來計算 minRows
shadow.value = 'x';
const singleRowHeight = shadow.scrollHeight;
const outerHeight = Math.max(minRows * singleRowHeight, contentHeight);
textarea.style.height = `${outerHeight}px`;
}
A complete implementation also needs to handle ResizeObserver. In MUI’s source code, there is a special workaround: it disconnects the ResizeObserver while adjusting the height and reconnects it on the next frame to avoid triggering the "ResizeObserver loop completed with undelivered notifications" error. Just this single edge case shows how tedious it is to maintain auto-resizing in JavaScript.
field-sizing: content
Now, CSS natively provides the field-sizing property, allowing you to achieve this in a single line:
textarea {
field-sizing: content;
}
That’s it. The browser will automatically adjust the size based on the form element’s content. If you want a default height, remember to set min-height or max-height. I usually like using rows to define height, but once field-sizing: content is applied, rows and cols no longer have any effect.
rows and cols attributes modify the default preferred size of a textarea. As a result, rows/cols have no effect on textarea elements with field-sizing: content set. MDN
When using field-sizing: content on its own, the element will expand indefinitely with its content. In practice, you’ll almost certainly pair it with min-width, max-width, min-height, and max-height to constrain the boundaries:
textarea {
field-sizing: content;
min-height: 3lh;
max-height: 10lh;
min-width: 200px;
max-width: 100%;
}
I recommend pairing it with lh units, which makes your intent much clearer. lh represents the element’s own line-height. 3lh equals the height of three lines of text, which is far more semantic than px or em and less prone to layout shifts when the font size changes.
The behavior works as follows:
- When the content is less than
min-height, it stays at the minimum height. - When the content exceeds
min-heightbut does not exceedmax-height, the height expands with the content. - When the content exceeds
max-height, the height locks atmax-heightand a scrollbar appears.
The width logic works the same way. Taking <input> as an example, it stays at min-width when there is little text, expands as you type, and stops growing once it hits max-width:
input {
field-sizing: content;
min-width: 100px;
max-width: 400px;
}
Supported Elements
field-sizing isn’t limited to <textarea>; it also supports <input> and <select>:
/* The input auto-adjusts its width according to the length of the typed text */
input {
field-sizing: content;
}
/* The select auto-adjusts its width according to the length of the selected option */
select {
field-sizing: content;
}
Browser Support for field-sizing
As of April 2026, Chrome 123+, Edge 123+, and Opera 109+ support field-sizing. Safari has added support in its Technology Preview releases, and Firefox is actively working on it.1
Overall, mainstream browser support is catching up fast. However, if your product still needs to support older browsers, you will currently need to keep a JavaScript fallback in place. Whenever I implement this, I include a feature check to see if the browser supports it:
const isFieldSizingSupported = CSS.supports('field-sizing', 'content');
While branching your code conditionally can be a bit of a hassle, CSS saves a significant amount of unnecessary JavaScript, so I’ll adopt it whenever possible.
Summary
field-sizing: content solves a long-standing problem that front-end developers have wrestled with for over a decade. In the past, regardless of the framework you used, you were fundamentally synchronizing the content height manually in JavaScript. Now that browsers handle this natively, it saves not only lines of code, but also the mental overhead of maintaining all those workarounds.
It gives me the same feeling as when I first realized scroll-behavior: smooth could replace an entire smooth-scroll library with one line. As browsers gradually bake these common patterns into native features, developers can spend their time focusing on what truly matters.
Footnotes
Related Posts
- Recreating My Room with Three.js Using React Three Fiber, I brought my real room into the browser—turning physical objects into an interactive table of contents, and using spatial memory to tell the story of my life and work over the past few years.
- Things to Keep in Mind When Using Images in Frontend Development Expanding on Jake Archibald's article, this post organizes how modern responsive images should be written: why width/height are still necessary, when to use CSS aspect-ratio, how to choose between AVIF and WebP, and using picture/source/srcset for art direction on mobile devices.
- Make Your Link Underlines Look Better: text-underline-offset By default, underlines sit very close to the text. Some designers dislike this look, and personally, I don't think it looks great either.
- Why the Web Shouldn't Strive for Pixel Perfection You should only focus on pixel perfection when it truly matters; otherwise, it often results in a lose-lose situation.