loke.dev
Header image for field-sizing Is the End of the Ghost Textarea Hack

field-sizing Is the End of the Ghost Textarea Hack

Stop using hidden ghost elements and scrollHeight listeners just to make your text inputs grow with their content.

· 4 min read

field-sizing Is the End of the Ghost Textarea Hack

We’ve spent the last decade gaslighting ourselves into thinking that measuring a hidden div just to resize a textarea was a perfectly reasonable way to build a web form. It was a messy, fragile dance: you'd mirror the text into a "ghost" element with the exact same styles, measure its height, and then manually push that pixel value back to the textarea.

If you weren't doing that, you were probably attaching input listeners to calculate scrollHeight and hoping you didn't trigger a layout thrash that killed your frame rate on mobile. It worked, mostly, but it always felt like a hack.

Now, we can finally stop.

The Old, Bloated Way

Just to remind ourselves how far we've come, this is what the "modern" JavaScript approach usually looked like. It’s not the worst code in the world, but it’s code that shouldn't have to exist:

const textarea = document.querySelector('.auto-expand');

textarea.addEventListener('input', () => {
  // Reset height so it can shrink if text is deleted
  textarea.style.height = 'auto';
  // Set the new height based on the scroll content
  textarea.style.height = `${textarea.scrollHeight}px`;
});

It looks simple until you realize you have to handle window resizing, initial content on page load, and the occasional CSS transition that throws the math off. It's extra JavaScript for a layout problem.

Enter field-sizing: content

The CSS Basic User Interface Module Level 4 quietly introduced a property that does exactly what we've always wanted: field-sizing.

By default, form elements like textarea, input, and select have a fixed size dictated by the browser (usually via the rows or cols attributes). When you set field-sizing: content, the element ignores those defaults and shrinks or grows to fit the text inside it.

.modern-textarea {
  field-sizing: content;
  
  /* Optional: give it a floor so it doesn't look like a tiny sliver when empty */
  min-height: 3lh; 
  
  /* Stop it from growing forever and breaking your layout */
  max-height: 400px;
}

That’s it. No listeners, no ghost elements, no weird height calculations. The browser handles the sizing during the layout pass, which is exactly where it belongs.

Why this is a win for performance

When you use the scrollHeight trick in JavaScript, you're often causing a forced synchronous layout. You change the content, the browser wants to wait to reflow, but then your script demands to know the scrollHeight immediately. The browser has to stop everything, calculate the layout, and give you the number.

field-sizing: content keeps everything inside the browser's native rendering engine. It knows the size of the text because it's already drawing it. It just applies that size to the box. It’s smoother, especially on lower-end devices where JavaScript-heavy interactions feel "janky."

Controlling the Chaos

You might be thinking, "If it grows with content, won't a user pasting a Tolstoy novel blow up my sidebar?"

Yes, it will. But we have existing CSS tools for that. field-sizing respects min-height, max-height, min-width, and max-width.

I’ve found that using the lh (line-height) unit is the secret sauce here. It lets you define sizes in terms of "lines of text" rather than arbitrary pixels.

textarea {
  field-sizing: content;
  width: 100%;
  padding: 0.5rem;
  
  /* Start with 2 lines of visible space */
  min-height: 2lh;
  
  /* Caps it at 10 lines before showing a scrollbar */
  max-height: 10lh;
}

It’s not just for textareas

While textareas are the biggest beneficiaries, field-sizing works on other inputs too. Ever tried to make an <input type="text"> that grows as the user types? It used to involve measuring the width of characters in a canvas or hidden span.

Now?

input[type="text"] {
  field-sizing: content;
  min-width: 5ch; /* Start with a width of roughly 5 characters */
}

The input will now expand horizontally as you type and shrink when you delete. It's incredibly useful for "inline editing" UI patterns where you want the input to look like part of a sentence.

The "Gotcha" (Browser Support)

This is the part where I have to be the bearer of semi-bad news. As of right now, field-sizing is primarily a Chromium feature (Chrome, Edge, etc.).

Firefox and Safari haven't fully shipped it yet, though it's on their radar. Because it’s a "progressive enhancement" type of feature, you can start using it today. If the browser doesn't support it, the textarea just behaves like a normal, fixed-size textarea.

If you absolutely *must* have the auto-grow behavior in every browser, you'll still need a tiny bit of JS as a fallback:

/* If the browser supports field-sizing, we don't need the hack */
@supports (field-sizing: content) {
  .auto-grow {
    field-sizing: content;
  }
}

Final Thoughts

I love it when CSS kills a common JavaScript dependency. It makes our codebases lighter and our UIs more resilient. The "ghost textarea" hack served us well for a decade, but it's time to let it retire.

If you’re building an internal tool or a project where you can nudge users toward a modern browser, try field-sizing: content. It’s one of those rare CSS properties that feels like it should have existed since 1998.