Forms✨ Premium

Textarea Auto-grow

The text area grows line by line up to 200 px, then scrolls; an "n / 200" counter turns amber at 150 and red at the limit.

JSResizeInteractive

Updated

This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Forms has 20 effects, including 2 free. Explore the category →

3 usage examples

Reply in a forum thread — a discreet box that opens as you write — Textarea Auto-grow example 1

① Reply in a forum thread — a discreet box that opens as you write

WhenUnder a gardener's comment, a member writes a reply: the box starts at two lines and lengthens as the advice takes shape.
WhyA small box does not intimidate and does not push the thread down; it only grows when the reply needs it, and the whole text stays visible while proofreading.
SettingsLight theme (--agrw-field:#fff, --agrw-border:#d5dccf, --agrw-focus:#4d7c0f, --agrw-text:#1f2a1c), --agrw-min-height:48px, --agrw-width:100%, --agrw-bg:transparent, --agrw-root-min-height:0. Typing driven by the scene, word by word.
Classified ad description — prepared text pasted in one go — Textarea Auto-grow example 2

② Classified ad description — prepared text pasted in one go

WhenThe seller pastes the description written in their notes: the box jumps to its maximum height, scrolls, and the counter shows 200 / 200 in red because the text was cut.
WhyThe red and the hard stop show straight away that the end of the text did not make it: the seller can shorten it before publishing instead of finding out on the live ad.
Settings--agrw-max-height:118px so scrolling kicks in early in a narrow column, orange accent (--agrw-focus:#ea580c, --agrw-warning:#c2410c, --agrw-limit:#dc2626). The paste is simulated by the scene: value cut at maxlength as the browser would, then an input event.
Bouquet card message — a short limit, on a phone — Textarea Auto-grow example 3

③ Bouquet card message — a short limit, on a phone

WhenWith a tap, the customer writes the note that will be copied onto the card: the preview fills up, the counter turns amber at 90 and stops at 120.
WhyThe printed card has a fixed space: the low limit and the colour change prevent a message that would not fit, with no rule to explain.
Settingsmaxlength="120" (the counter shows "/ 120" and amber arrives at 90), --agrw-max-height:118px to keep the button visible on the small screen, pink tones (--agrw-focus:#be185d). The card preview and the tap circle belong to the scene.

How it works

On every input event, the script resets the height to auto, reads scrollHeight, adds the border thickness and applies the result, capped by the field's computed max-height (200 px by default). While the text fits there is no scrollbar; beyond the cap, overflow-y switches to auto and the area scrolls. A ResizeObserver recomputes when the width changes (phone rotation, narrowing column).

The counter shows the value's length over the maximum read from the maxlength attribute (200 shipped); the number after the slash is rewritten by the script, so it follows your attribute. At 75% (150 of 200) it gets the is-warning class, amber; at the limit, is-limit, red.

The <textarea> tag is empty in the shipped HTML: the placeholder shows and the counter starts at 0. The script is wrapped in a function: pasted twice, or next to an existing textarea variable, it throws no error, and a root already set up (data-agrw-ready) is not wired again.

Accessibility

  • Label: "Message" in a visually hidden <label>; rename it to fit your form ("Your reply", "Description").
  • Linked counter: the area points to the counter through aria-describedby, read when it is reached with the keyboard.
  • Thresholds announced, not every key: an aria-live="polite" region says "50 characters left" when entering amber, then "200-character limit reached"; nothing is read in between.
  • Motion: the height changes without animation, nothing to switch off under prefers-reduced-motion.
  • Limit: beyond 200 px the area scrolls; for very long text, a fixed height shown from the start remains more comfortable.

Browser compatibility

2.7 KB of JavaScript, CSS with variables. Measured on 08/11/2026 in Chromium, Firefox and WebKit (Playwright): empty initial value, visible placeholder, "1 / 200" after one key, height following the lines then stopping at 200 px with scrolling, paste cut at 200, two independent copies, form reset, no errors. ResizeObserver is the most recent feature (Chrome 64, Firefox 69, Safari 13.1, according to @mdn/browser-compat-data).

Chrome 64+✓ Full (measured)
Firefox 69+✓ Full (measured)
Safari 13.1+✓ Full (measured in WebKit)
Edge 79+✓ Full (Chromium engine)
iOS Safari 13.4+✓ Not checked on a real device
Android Chrome 64+✓ Full (Chromium engine)

Without ResizeObserver (Chrome 51-63, Safari 10-13), the area grows while typing but is not recomputed when only its width changes. Without JavaScript: fixed two-line area, counter stuck at 0, maxlength still enforced by the browser.

The code

HTML structure to paste into your page (the CSS + JS come with your purchase):

index.html — structure
<div class="fx-0289-root">
  <div class="agrw-group">
    <label class="agrw-field">
      <span class="agrw-sr">Message</span>
      <textarea class="agrw-input" name="message" placeholder="Type your message..." maxlength="200"></textarea>
    </label>
    <div class="agrw-count"><span class="agrw-current">0</span> / <span class="agrw-max">200</span></div>
    <span class="agrw-sr agrw-status" aria-live="polite"></span>
    <span class="agrw-words" hidden>
      <span data-msg="warning">{n} characters left</span>
      <span data-msg="limit">{max}-character limit reached</span>
    </span>
  </div>
</div>

One-time payment, no subscription — the code is yours forever

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Character limit (HTML — maxlength on the area) 200 The counter and its thresholds follow: with 500, amber arrives at 375. The number after the slash is rewritten by the script.
Heights (CSS — --agrw-min-height, --agrw-max-height on .fx-0289-root) 60px · 200px Starting height and cap before scrolling. The script reads the computed cap, nothing to change in the JS.
Warning threshold (JS — Math.ceil(max * 0.75)) 75% of the maximum Switch to 0.9 to warn only near the limit.
Counter colours (CSS — --agrw-muted, --agrw-warning, --agrw-limit) #8b8b93 · #f59e0b · #ef4444 On a light background, prefer #c2410c and #dc2626 for enough contrast.
Field (CSS — --agrw-field, --agrw-border, --agrw-focus, --agrw-focus-field, --agrw-text) 5% white · 10% white · #6366f1 · 5% indigo · #fff Background, border, focus colour and background, typed text.
Width and frame (CSS — --agrw-width, --agrw-bg, --agrw-root-min-height) 280px · #0a0a0f · 300px On a real page: --agrw-width:100%; --agrw-bg:transparent; --agrw-root-min-height:0.
Announced messages (HTML — .agrw-words) "{n} characters left" · "{max}-character limit reached" {n} becomes the number left, {max} the limit. Translate them here.

FAQ

Yes: the script computes height and counter once on setup, then after every form reset. If you change the value from a script, then fire textarea.dispatchEvent(new Event('input', {bubbles: true})).
Your HTML has invisible text between <textarea> and </textarea> (a line break, spaces): it becomes the starting value, hides the placeholder and counts towards the limit. Keep both tags touching when you edit the HTML.
Removing .agrw-count from the HTML stops the script from setting up, as it needs it. To keep only the growing, leave the element and hide it in CSS (.fx-0289-root .agrw-count{display:none}): the field still points to it through aria-describedby, so screen readers keep hearing the character count, which stays useful.