Standard <textarea> does not autogrow while you add new rows:

text area without autogrow

Improved <textarea> with autogrow:

text area with autogrow

CSS has "field-sizing: content", that lets you autogrow your text inputs.

TailwindCSS has field-sizing-content.

But field-sizing is not supported by all browsers.

So you do have to use JS instead.

After many iterations, here’s my perfected JS approach:

31.12.2024 version: #

// autogrow_controller.js
import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
  static values = {
    maxHeight: { type: Number, default: 15 },
  };

  connect() {
    this.resize();
  }

  resize() {
    const textarea = this.element;
    textarea.style.height = "auto";
    textarea.style.overflowY = "hidden";

    // while textarea scrollHeight > textarea offsetHeight & textarea offsetHeight < this.maxHeightValue (5) rows * height of 1 row
    while (
      textarea.scrollHeight > textarea.offsetHeight &&
      textarea.offsetHeight <
        this.maxHeightValue * parseFloat(getComputedStyle(textarea).lineHeight)
    ) {
      // increase height of textarea by one row
      textarea.style.height = `${
        textarea.offsetHeight +
        parseFloat(getComputedStyle(textarea).lineHeight)
      }px`;
    }
    // if textarea already has > maxHeight rows, activate scroll
    if (
      textarea.offsetHeight >=
      this.maxHeightValue * parseFloat(getComputedStyle(textarea).lineHeight)
    ) {
      textarea.style.overflowY = "auto";
    }
  }
}

14.06.2023 version: #

Just connect the below stimulus controller to a <textarea> and you’re good to go!

rails g stimulus autogrow

StimulusJS controller inspired by MDN HTMLTextAreaElement example:

import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
  connect() {
    this.element.style.overflow = "hidden";
    this.grow();
  }

  grow() {
    this.element.style.height = "auto";
    this.element.style.height = `${this.element.scrollHeight}px`;
  }
}

Usage with html.erb:

<%= form.text_area :content,
                    # rows: 5,
                    data: { controller: 'autogrow',
                            action: "input->autogrow#grow" } %>

31.03.2022 version: #

// app/javascript/controllers/autogrow.js
import { Controller } from "@hotwired/stimulus";

// Connects to data-controller="autogrow"
// <%= form.text_area :content, data: {controller: "autogrow" } %>
// <textarea data-controller="autogrow" name="article[content]"></textarea>

export default class extends Controller {
  initialize() {
    this.autogrow = this.autogrow.bind(this);
  }

  connect() {
    this.element.style.overflow = "hidden";
    this.autogrow();
    this.element.addEventListener("input", this.autogrow);
    window.addEventListener("resize", this.autogrow);
  }

  disconnect() {
    window.removeEventListener("resize", this.autogrow);
  }

  autogrow() {
    this.element.style.height = "auto";
    this.element.style.height = `${this.element.scrollHeight}px`;
  }
}

This old version is based on the fantastic @guillaumebriday’s stimulus-textarea-autogrow