CSS field-sizing: form fields that fit their text

One CSS line makes an input widen, a textarea grow and a select fit its option. Add limits and a small fallback, and it is ready for real forms.

Add field-sizing: content to a form control and it sizes itself to what is inside. An <input> widens as you type, a <textarea> grows a line at a time, and a <select> fits the chosen option.

Pair it with minimum and maximum limits, and keep a short script for browsers without it.

Try it first. Type a longer name, add lines, pick the long option.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>field-sizing: content</title>
<style>
  body { margin: 0; padding: 18px; font: 16px/1.45 system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  label { display: block; margin: 0 0 14px; font-size: 13px; color: #4b5563; }
  input, textarea, select {
    display: block; margin-top: 4px;
    font: inherit; padding: 6px 10px; box-sizing: border-box;
    border: 1px solid #c9ced6; border-radius: 8px; background: #fff;
  }

  /* The whole trick: size the box to what is inside it */
  .fit { field-sizing: content; }

  /* Limits, so the box never gets too small or too wide */
  input.fit    { min-width: 6ch; max-width: 100%; }
  textarea.fit { min-width: 12ch; max-width: 100%; min-height: 2lh; }
</style>
</head>
<body>
  <label>Input (type a longer name)
    <input class="fit" value="Ada">
  </label>
  <label>Textarea (press Enter for new lines)
    <textarea class="fit">One line to start.</textarea>
  </label>
  <label>Select (pick the long option)
    <select class="fit">
      <option>Short</option>
      <option>A much longer option label</option>
    </select>
  </label>
</body>
</html>
One CSS property on three kinds of field. Edit the code and the example reruns.

The CSS that does the work is one line, plus limits:

.fit { field-sizing: content; }

input.fit    { min-width: 6ch; max-width: 100%; }
textarea.fit { min-width: 12ch; max-width: 100%; min-height: 2lh; }

What field-sizing changes

Form fields normally have a fixed default size. The browser picks a width for an input and a number of rows for a textarea, and the text scrolls inside that box. That default is field-sizing: fixed.

Left: the default size cuts text off. Right: field-sizing: content follows it.
Left: the default size cuts text off. Right: field-sizing: content follows it.

With field-sizing: content the box measures its content instead:

Element Size with field-sizing: content
<input> Width follows the typed value, or the placeholder when empty
<textarea> Height follows the lines, width follows the longest line
<select> Width follows the selected option, not the longest one

The property does not change how the value is read or sent. input.value and form data work as before.

Why min and max limits matter

Content sizing has two bad edges. An empty input with no placeholder shrinks to a sliver that is hard to tap. A long line makes the box wider than its container, and the page can scroll sideways.

Without limits the box gets too small and then too big. With min and max it stays usable.
Without limits the box gets too small and then too big. With min and max it stays usable.

Press Fill and Clear in the example below to see both edges, and how limits fix them.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>field-sizing with and without limits</title>
<style>
  body { margin: 0; padding: 14px; font: 15px/1.45 system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; }
  .col { background: #fff; border: 1px solid #e1e4ea; border-radius: 10px; padding: 10px 12px; min-width: 0; overflow: hidden; }
  h3 { margin: 0 0 8px; font-size: 13px; }
  .col.bad h3 { color: #9a3412; } .col.good h3 { color: #0f5132; }
  input, textarea {
    display: block; margin: 0 0 10px; font: inherit; padding: 5px 8px;
    border: 1px solid #c9ced6; border-radius: 6px;
    box-sizing: border-box;  /* so max-width: 100% includes padding and border */
    field-sizing: content;
  }

  /* Right column only: a floor and a ceiling */
  .good input    { min-width: 8ch; max-width: 100%; }
  .good textarea { min-width: 100%; min-height: 2lh; max-height: 4lh; }
  button { font: inherit; font-size: 13px; padding: 4px 10px; }
</style>
</head>
<body>
<div class="grid">
  <div class="col bad">
    <h3>No limits</h3>
    <input id="in1" value="">
    <textarea id="ta1"></textarea>
  </div>
  <div class="col good">
    <h3>min and max set</h3>
    <input id="in2" value="">
    <textarea id="ta2"></textarea>
  </div>
</div>
<p><button id="fill">Fill both with long text</button> <button id="clear">Clear both</button></p>

<script>
  const long = 'A long line of text that keeps going past the edge of the column.'
    + '\nLine two\nLine three\nLine four\nLine five\nLine six\nLine seven\nLine eight';
  const boxes = document.querySelectorAll('input, textarea');
  document.getElementById('fill').addEventListener('click', () => {
    boxes.forEach((b) => { b.value = b.tagName === 'INPUT' ? long.split('\n')[0] : long; });
  });
  document.getElementById('clear').addEventListener('click', () => {
    boxes.forEach((b) => { b.value = ''; });
  });
</script>
</body>
</html>
Left: no limits. Right: min-width, max-width, min-height and max-height.
  • min-width on inputs keeps an empty field clickable. ch units, the width of the zero character, suit text fields.
  • max-width: 100% stops a field at its container. Add box-sizing: border-box, or padding and border push it past the edge.
  • min-height on a textarea replaces the rows attribute, which content sizing ignores. The lh unit is one line height.
  • max-height stops a textarea from growing. After that it scrolls, like a normal textarea.

Check support before you rely on it

field-sizing is newer than most form CSS. When we tested in September 2026, the Chromium engine (Chrome, Edge) and the WebKit engine (Safari) applied it, and the Firefox engine ignored it.

Where it is ignored, fields keep their normal fixed size, so nothing breaks. They just do not grow.

JavaScript can ask the browser directly:

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

CSS has the same test as a block. Use it to give unsupported browsers a taller fixed textarea instead:

@supports not (field-sizing: content) {
  textarea { min-height: 6lh; }
}

The JavaScript fallback for textareas

For a textarea, the fallback is short. On every input event, set the height to auto so the box can shrink, then set it from scrollHeight. Only run it where CSS cannot do the job.

One support check picks the path. The same min and max limits apply to both.
One support check picks the path. The same min and max limits apply to both.
const box = document.querySelector('textarea');

if (!CSS.supports('field-sizing', 'content')) {
  box.addEventListener('input', () => {
    box.style.height = 'auto';
    box.style.height = box.scrollHeight + 'px';
  });
}

scrollHeight includes padding but not the border, so this exact line fits a textarea with box-sizing: border-box only after you add the border widths. The textarea guide covers that arithmetic and the other textarea basics.

For an input, a rough fallback sets its size attribute to the number of characters typed. size counts average character widths, so the fit is approximate.

A finished example: a message box

A chat composer is the classic use. The box starts at one line, grows to six, then scrolls. It uses CSS where supported and the script everywhere else. Tick Force the JS fallback to watch the script path in any browser.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Growing message box with fallback</title>
<style>
  body { margin: 0; padding: 14px; font: 15px/1.45 system-ui, sans-serif; background: #eceef1; color: #1d2330; }
  .chat { max-width: 520px; margin: 0 auto; background: #fff; border-radius: 12px; box-shadow: 0 4px 16px rgba(0,0,0,.08); overflow: hidden; }
  .log { height: 170px; overflow-y: auto; padding: 12px; display: flex; flex-direction: column; gap: 6px; }
  .msg { align-self: flex-end; max-width: 80%; background: #2563eb; color: #fff; padding: 6px 11px; border-radius: 12px; white-space: pre-wrap; overflow-wrap: anywhere; }
  form { display: flex; gap: 8px; align-items: flex-end; padding: 10px; border-top: 1px solid #e5e7eb; }
  textarea {
    flex: 1; font: inherit; padding: 8px 10px; resize: none;
    border: 1px solid #c9ced6; border-radius: 10px;
    field-sizing: content;
    min-height: 1lh; max-height: 6lh;  /* 1 to 6 lines, then it scrolls */
    box-sizing: content-box;
  }
  button { font: inherit; padding: 8px 14px; border: 0; border-radius: 10px; background: #2563eb; color: #fff; }
  .meta { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 6px; padding: 0 12px 10px; font-size: 12.5px; color: #6b7280; }
  .meta b { color: #0f5132; }

  /* Test switch: turn CSS sizing off to watch the JavaScript fallback */
  .force-js textarea { field-sizing: fixed; }
</style>
</head>
<body>
<div class="chat" id="chat">
  <div class="log" id="log"><div class="msg">Type a few lines below.</div></div>
  <form id="form">
    <textarea id="box" rows="1" placeholder="Message"></textarea>
    <button>Send</button>
  </form>
  <div class="meta">
    <span>Sizing by: <b id="mode"></b></span>
    <label><input type="checkbox" id="force"> Force the JS fallback</label>
  </div>
</div>

<script>
  const box = document.getElementById('box');
  const chat = document.getElementById('chat');
  const mode = document.getElementById('mode');
  const cssWorks = CSS.supports('field-sizing', 'content');
  let useJs = !cssWorks;

  // Fallback: shrink to auto, then grow to the content height
  function fit() {
    if (!useJs) return;
    box.style.height = 'auto';
    box.style.height = box.scrollHeight - 16 + 'px';  // 16 = top + bottom padding (content-box)
  }

  function setMode() {
    chat.classList.toggle('force-js', useJs && cssWorks);
    if (!useJs) box.style.height = '';  // hand sizing back to CSS
    mode.textContent = useJs ? 'JavaScript fallback' : 'CSS field-sizing';
    fit();
  }

  box.addEventListener('input', fit);

  document.getElementById('force').addEventListener('change', (e) => {
    useJs = e.target.checked || !cssWorks;
    setMode();
  });

  // Enter sends, Shift+Enter adds a line
  box.addEventListener('keydown', (e) => {
    if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); document.getElementById('form').requestSubmit(); }
  });

  document.getElementById('form').addEventListener('submit', (e) => {
    e.preventDefault();
    const text = box.value.trim();
    if (!text) return;
    const div = document.createElement('div');
    div.className = 'msg';
    div.textContent = text;
    const log = document.getElementById('log');
    log.append(div);
    log.scrollTop = log.scrollHeight;
    box.value = '';
    fit();
    box.focus();
  });

  setMode();
</script>
</body>
</html>
Grows from one line to six, then scrolls. Enter sends, Shift+Enter adds a line.

The parts:

  1. field-sizing: content with min-height: 1lh and max-height: 6lh.
  2. resize: none so a dragged handle does not fix the height.
  3. The fallback, run only when the support check fails.
  4. Reset after sending. Clearing the value shrinks the CSS path by itself. The script path needs one more call to the sizing function.

When it does not work

What you see Cause Fix
The field never changes size The browser does not support the property Check with CSS.supports and add the fallback
An empty input is a thin sliver Nothing to size to min-width or a placeholder
The field runs off the right edge No maximum max-width: 100%
It still sticks out a few pixels Padding and border are added to the max box-sizing: border-box
rows or size has no effect Content sizing replaces them min-height or min-width
It stopped growing after a resize The resize handle set a fixed height resize: none, or accept the fixed size
The box does not grow at all An explicit width or height wins Remove it, use min and max

A form that grows as you type is easier to try than to describe. A screenshot shows one size, and a pasted snippet cannot be typed into. For single-line fields, see the text input guide, and for dropdowns, the select tag guide.

To send the working version, paste the page into a NOS document and choose Create share link. HTML to link walks through it.

The page renders as written and its scripts run, so the people you send it to can type into the fields themselves. If you change the code later, the same link shows the new version.

Questions people ask

What does field-sizing: content do?

It sizes a form control to its content instead of a fixed default size. An input gets as wide as its value, a textarea as tall (and wide) as its text, and a select as wide as the chosen option.

Does field-sizing work in every browser?

No. When we tested in September 2026, the Chromium engine (used by Chrome and Edge) and the WebKit engine (used by Safari) applied it, and the Firefox engine ignored it. Check in code with CSS.supports() and keep a fallback.

Why does my empty input shrink to almost nothing?

With field-sizing: content, an empty input with no placeholder has almost no content to size to. Give it a min-width, for example 8ch, or a placeholder, which the box then sizes to.

Why do rows and size stop working?

With field-sizing: content, the content decides the size, so the rows attribute of a textarea and the size attribute of an input no longer set it. Use min-height and min-width in CSS for the starting size instead.

How do I stop the textarea from growing forever?

Set max-height. When the text is taller than that, the textarea stops growing and shows a scrollbar, the same as a fixed textarea.

Keep reading