CSS field-sizing: Auto-Growing Textareas
Photo by Unsplash on Unsplash
Table of Contents
The one line that deletes your resize script
Every project I've worked on had a version of the same file. `autosize.js`, or `useAutoResize.ts`, or twenty lines copied off Stack Overflow that set `style.height = 'auto'` and then `style.height = scrollHeight + 'px'` on every keystroke.
It always worked, right up until it didn't. Paste a wall of text and it jumped. Load a value from the server and it stayed one row until you touched it. Put it in a modal that starts hidden and `scrollHeight` came back as zero.
All of that is now one CSS declaration. I'm still slightly annoyed about the years I spent on the alternative.
What it actually does
`field-sizing: content` flips that relationship. The control sizes itself to its contents instead.
```css textarea { field-sizing: content; } ```
That's it. Type and it grows. Delete and it shrinks. Paste six paragraphs and it expands to fit them in one frame, with no flicker and no layout thrash, because the browser is doing this during layout rather than after an event fires.
The property takes two values. `fixed` is the default behaviour you already know. `content` is the new one.
It applies to more than textareas. Text inputs grow horizontally with what's typed, which is genuinely useful for tag entry fields and inline editing. Select elements size to their selected option rather than the widest one, which fixes a small annoyance that has existed forever.
MDN's field-sizing reference has the full value list and the details on how it interacts with the `rows` and `size` attributes, which is worth a skim because the interaction is not quite what you'd guess.
Clamping it so it does not run away
You clamp it the normal CSS way:
```css textarea { field-sizing: content; min-height: 4lh; max-height: 16lh; } ```
I'm using `lh` there, the line height unit, because for a text control that's what you actually mean. Four lines minimum, sixteen lines maximum. Past sixteen it starts scrolling internally, which is the correct behaviour.
You could use `rem` or `px` instead and it works fine, it just requires you to do arithmetic against your line height every time you change the type scale. If you're fuzzy on which unit does what, the CSS units rundown covers the family, though `lh` is a newer addition to it.
For a single line input growing horizontally, the same idea with width:
```css input.tag-entry { field-sizing: content; min-width: 6ch; max-width: 100%; } ```
`ch` is roughly the width of a zero character in the current font, so `min-width: 6ch` means "always at least six characters wide". That stops an empty input collapsing to a sliver, which looks broken and is impossible to click.
The `max-width: 100%` matters more than it looks. Without it, a long unbroken string pushes the input past its container and you get horizontal overflow on the whole page. I've shipped that bug. Once.
Where it fits with the rest of the layout
Inside a flex row, a growing textarea will stretch its siblings by default because `align-items` is `stretch`. Usually you want `align-items: flex-start` or `flex-end` so a growing composer doesn't drag a send button into a comically tall shape.
```css .composer { display: flex; align-items: flex-end; gap: .5rem; } ```
That's the chat input pattern, and it's the single most common real use for this property. Message box grows upward, send button stays put at the bottom. Building that with JavaScript was fiddly enough that plenty of products just shipped a fixed height box and let it scroll.
In a grid, a growing control in an `auto` sized row does the sensible thing. In a fixed row track it will overflow, so check your track sizing if growth appears to do nothing.
The container query angle is worth thinking about too, because a control that sizes to content and a container that responds to size can chase each other if you're careless. Keep the query on an ancestor that isn't itself sized by the growing element, and the pattern in the container queries walkthrough stays stable.
Scrollbar behaviour is the last small thing. Once you hit `max-height`, the control scrolls internally. Set `scrollbar-gutter: stable` if you don't want a one pixel horizontal shift the moment the scrollbar appears.
A gotcha with controlled React inputs
If you're rendering a textarea in React as a controlled component, the value changes on every keystroke and the browser resizes during layout. That's fine. What's not fine is if you also kept an old autosize hook running, because now two systems are fighting over the height and you get a visible stutter on fast typing.
Delete the hook. Not "disable it in a branch", delete it. I spent twenty minutes convinced `field-sizing` was buggy before finding a `useLayoutEffect` three files away still setting an inline height, and inline styles win over the stylesheet every time.
The same warning applies to any CSS-in-JS or utility class that sets an explicit `height`. `field-sizing: content` computes a size, but an explicit `height` declaration on the same element overrides it. If growth appears to do nothing at all, check the computed styles panel for a height coming from somewhere you forgot about. That was the cause in both cases where a colleague asked me why it wasn't working.
Server rendered values are the pleasant surprise here. Because sizing happens at layout, a textarea that arrives from the server already full of text renders at the right height on first paint, with no effect needing to run and no jump.
Support, and what to do about older browsers
The good news is that this degrades about as gracefully as a feature can. A browser that doesn't understand the declaration ignores it entirely and you get a normal fixed size textarea. Nothing breaks, nothing looks wrong, the box just doesn't grow.
That makes it a genuine progressive enhancement, which is rarer than it sounds. You can ship it today with no fallback and the worst case is the behaviour everyone had last year.
If you need the growth specifically on older browsers, feature detection is one line:
```css @supports not (field-sizing: content) { /* keep the old JS autosize, or accept a taller default */ textarea { min-height: 8lh; } } ```
Personally I'd delete the JavaScript and let old browsers have a slightly taller default box. The script was always the buggy part, and carrying it around to serve a shrinking minority is how codebases get heavy. If you want to sanity check character limits while you're rebuilding a form, the word counter is handy for working out what a realistic `max-height` should be for the content people actually submit.
Frequently Asked Questions
What does field-sizing: content do?
It makes a form control size itself to its contents rather than to a fixed rows or size attribute. A textarea grows and shrinks as you type, a text input widens with what's entered, and a select sizes to the chosen option. The default value is fixed, which is the old behaviour.
Do I still need a JavaScript autosize script for textareas?
Not in browsers that support field-sizing, which now covers Chrome, Edge and Firefox. The CSS version is better because it happens during layout rather than after an event, so it handles pasted text and server-loaded values without the flicker that scrollHeight based scripts produce.
How do I stop a textarea growing forever?
Add max-height alongside it, and min-height for the floor. Using the lh unit is neatest since it maps to lines of text, so max-height 16lh means sixteen lines before it starts scrolling internally. Without a cap, a long paste will stretch your layout arbitrarily.
Which browsers support CSS field-sizing?
Chrome and Edge shipped it first, and Firefox added it in version 152, which made it Baseline newly available in June 2026. Check Safari's current status before depending on it. Newly available means recently interoperable, not universally safe on older devices.
What happens in browsers that don't support field-sizing?
The declaration is ignored and you get a normal fixed size control. Nothing breaks visually, so it works as a clean progressive enhancement you can ship without a fallback. If you want to branch explicitly, @supports not (field-sizing: content) lets you target the gap.
Does field-sizing work on inputs and selects too?
Yes. Text inputs grow horizontally with their value, which suits tag entry and inline editing, and select elements size to the selected option instead of the widest one in the list. For inputs you'll usually want a min-width in ch units so an empty field stays clickable, and the [CSS units guide](/blog/css-units-explained-px-em-rem-vh-vw) covers what ch actually measures.
Try ToolsFuel
23+ free online tools for developers, designers, and everyone. No signup required.
Browse All Tools