CSS · 01 of 05

A field that grows with you

Let the browser size your textarea. Keep editing and scrolling available in the fallback.

field-sizingBaseline Newly availableMDN field-sizing
Experience kept
A field that grows, scrolls, and remains manually resizable
Machinery removed
Two listeners, height measurement, and inline style mutation
Decision
Progressively enhance with CSS
Live demo

Type a little. Paste a lot. Clear it.

THE REDUCTION

Same outcome. Less to coordinate.

Measuring a field in JavaScript to resize it is work the browser can do.

CURRENT PROJECT · JS
const resize = (field) => {
  field.style.height = "auto";
  field.style.height = `${field.scrollHeight}px`;
};

field.addEventListener("input", () => resize(field));
window.addEventListener("resize", () => resize(field));
WITH FEWER PARTS · CSS
.comment-field {
  field-sizing: content;
  min-block-size: 3lh;
  max-block-size: 12lh;
}

Newly available, so the base stays a conventional resizable textarea with a sensible minimum height. Nothing breaks where the property is ignored.

ABOUT THE PATTERN

Why this works

The control follows its content without a resize observer, mirrored element, or input handler.

Decisions that matter

  • Constrain inline size so a textarea grows vertically, and set minimum/maximum block sizes. Leave overflow available when the maximum is reached.
  • Do not keep a fixed height or an old autosizing script's inline height that defeats native growth. Remove measurement code only after checking what else its listeners do.
  • Placeholders influence content sizing; rows and cols no longer set the preferred size under field-sizing: content. The demo keeps rows for its base and sets explicit CSS size bounds for the enhancement.
  • The base is a labelled, manually resizable, scrollable textarea. Older browsers keep that behavior when they ignore the enhancement. If autosizing itself is required on older targets, this base is insufficient: preserve the tested JavaScript autosizer there, gated by CSS.supports('field-sizing', 'content').
  • Keep app state, validation, announcements, and submission logic. Demo buttons only change sample content; native sizing requires no JavaScript.

Read from the skill's own reference files at build time.

COPY, ADAPT, SHIP

The dependency-free implementation

HTML, CSS, and the JavaScript the interaction actually needs

STANDALONE HTML
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Content-sized textarea · Fewer Parts</title>
<style id="recipe">
/* Component base: usable without field-sizing support. */
.content-field {
  box-sizing: border-box;
  inline-size: 100%;
  min-block-size: 7rem;
  max-block-size: 18rem;
  overflow: auto;
  resize: vertical;
  font: inherit;
}
@supports (field-sizing: content) {
  .content-field { field-sizing: content; }
}
</style>
<style>
/* Showcase presentation; not required by the component. */
:root { color-scheme: light; font: 17px/1.55 system-ui, sans-serif; color: #202b25; background: #f5f4ee; }
* { box-sizing: border-box; }
body { margin: 0; padding: clamp(1rem, 5vw, 4rem); }
main { max-inline-size: 64rem; margin-inline: auto; }
.eyebrow { font-size: .75rem; letter-spacing: .13em; text-transform: uppercase; color: #486151; }
h1 { font-size: clamp(2rem, 5vw, 3.8rem); line-height: 1.08; letter-spacing: -.04em; max-inline-size: 16ch; margin-block: 1rem; }
.intro { max-inline-size: 56ch; color: #4c5e52; }
.badge { display: inline-block; font-size: .8rem; padding: .3rem .7rem; background: #e2ebdc; border-radius: 2rem; }
.layout { display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); gap: 2rem; margin-block-start: 2rem; }
.panel { padding: clamp(1rem, 3vw, 2rem); border: 1px solid #ccd5ca; border-radius: 1rem; background: #fff; min-inline-size: 0; }
h2 { margin-block-start: 0; font-size: 1.15rem; }
label[for="message"] { display: block; font-weight: 650; margin-block-end: .5rem; }
textarea { display: block; padding: .8rem; border: 1px solid #7b907e; border-radius: .5rem; background: #fcfdf9; color: inherit; }
.controls { display: flex; flex-wrap: wrap; gap: .5rem; margin-block: 1rem; }
button { font: inherit; font-size: .85rem; color: #243d2d; background: #edf3e9; border: 1px solid #acbfa8; border-radius: .4rem; padding: .5rem .7rem; cursor: pointer; }
button:hover { background: #dce9d5; }
:focus-visible { outline: 3px solid #276ac5; outline-offset: 3px; }
.hint { color: #4c5e52; font-size: .85rem; }
input[type="checkbox"] { accent-color: #315b35; }
pre { overflow: auto; background: #f5f7f1; padding: 1rem; border-radius: .5rem; font-size: .78rem; }
a { color: #245d3b; }
footer { margin-block-start: 2rem; font-size: .8rem; }
/* Explicit fallback simulation, not component code. */
.force-fallback .content-field { field-sizing: fixed; }
@media (max-width: 48rem) { .layout { grid-template-columns: 1fr; } }
</style>
<main>
  <p class="eyebrow">Fewer Parts / Recipe 01</p>
  <h1>A little more room for your words.</h1>
  <p class="intro">A textarea that grows as you type and shrinks when you delete. At its height limit, the content scrolls.</p>
  <span class="badge">field-sizing · Newly Available in June 2026</span>
  <div class="layout">
    <section class="panel" aria-labelledby="demo-heading">
      <h2 id="demo-heading">Try it</h2>
      <!-- Component markup. Showcase controls below are optional. -->
      <label for="message">Your message</label>
      <textarea class="content-field" id="message" rows="4" aria-describedby="field-help" placeholder="Start with a few words…"></textarea>
      <p class="hint" id="field-help">Type, paste, or delete text. You can also resize the field manually.</p>
      <div class="controls">
        <button type="button" data-action="short">Short text</button>
        <button type="button" data-action="long">Long text</button>
        <button type="button" data-action="word">Unbroken text</button>
        <button type="button" data-action="clear">Clear</button>
      </div>
      <label><input type="checkbox" id="fallback"> Use fallback</label>
      <p class="hint" role="status" id="support">Without demo JavaScript, the field still works. Native autosizing depends on your browser.</p>
    </section>
    <section class="panel" aria-labelledby="code-heading">
      <h2 id="code-heading">The component CSS</h2>
      <p class="hint">This preview reads the CSS used by the field above. No measurement script is needed.</p>
      <pre><code id="source">Enable JavaScript to display the recipe source, or view this file's source.</code></pre>
      <p class="hint">The fallback preserves editing, manual resizing, and scrolling. It does not provide automatic growth.</p>
    </section>
  </div>
  <footer>Support checked 14 September 2026 · <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/field-sizing">Live documentation and compatibility</a></footer>
</main>
<script>
// Showcase controls only; CSS performs the sizing.
const field = document.querySelector('#message');
const fallback = document.querySelector('#fallback');
const support = document.querySelector('#support');
document.querySelector('#source').textContent = document.querySelector('#recipe').textContent.trim();
function updateSupport() {
  document.documentElement.classList.toggle('force-fallback', fallback.checked);
  support.textContent = fallback.checked
    ? 'Fallback preview: manual resizing and scrolling remain available.'
    : CSS.supports('field-sizing', 'content')
      ? 'Your browser reports field-sizing support. Try adding and deleting text.'
      : 'Your browser uses the fallback: manual resizing and scrolling.';
}
fallback.addEventListener('change', updateSupport);
document.querySelectorAll('[data-action]').forEach(button => {
  button.addEventListener('click', () => {
    const samples = {
      short: 'A small thought, with room to grow.',
      long: Array(12).fill('A good interface makes space for a longer thought. Add details, edit them, then clear the field to watch it settle back.').join('\n\n'),
      word: 'LongUnbrokenText'.repeat(60),
      clear: ''
    };
    field.value = samples[button.dataset.action];
    field.scrollTop = 0;
    field.dispatchEvent(new Event('input', { bubbles: true }));
  });
});
updateSupport();
</script>
</html>
Using Tailwind CSS v4 already? View the adapted example

Optional integration · adapt utilities to your project tokens

TAILWIND HTML
<!-- Tailwind CSS v4. Adapt colors and spacing to the project's tokens. -->
<label for="message" class="block font-medium text-slate-900">Your message</label>
<textarea
  id="message"
  name="message"
  rows="4"
  aria-describedby="message-help"
  placeholder="Start with a few words…"
  class="mt-2 block min-h-28 max-h-72 w-full resize-y overflow-auto rounded-lg border border-slate-300 bg-white p-3 text-slate-900 field-sizing-content placeholder:text-slate-400 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
></textarea>
<p id="message-help" class="mt-2 text-sm text-slate-600">
  The field grows with your message and scrolls after reaching its height limit.
</p>