Auto-Resize Textarea — Free HTML CSS JS Snippet

Auto-Resize Textarea · Forms · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

True auto-resize
Resets height to auto before reading scrollHeight — ensures accurate shrink as well as grow, not just a one-way expand.
Three-threshold counter
Muted grey below 80%, amber warning at 80%, red error at 100%. Both textarea border and counter text change colour in sync.
Connected border design
Textarea and counter bar share a border with matching border-radius corners, appearing as a single component. Border colour transitions propagate via sibling selectors.
Star rating widget
Three-listener pattern: mouseenter previews, mouseleave resets, click commits. State is a simple integer — easy to read on submit.
Success state
On valid submit the form hides and a green success panel with icon and copy appears. "Submit another" resets all state.
Client validation
Textarea is required. Submit handler checks .trim() and adds the error class to focus and highlight the empty field.
Accessible counter
aria-live="polite" on the counter and aria-describedby linking hint + counter to the textarea for screen reader compatibility.
Overflow:hidden on textarea
Prevents a scrollbar flash during the height recalculation. Combined with resize:none it removes all default browser resize affordances.

About this UI Snippet

Auto-Resize Textarea — HTML CSS JavaScript

Screenshot of the Auto-Resize Textarea snippet rendered live

Textarea that grows as you type with a live character counter, warning colours at 80%/100%, star rating, and form submit state. Zero dependencies.

An auto-resizing textarea removes one of the most frustrating friction points in form UX: the fixed-height text box that forces users to scroll through their own input while typing. This pattern is used in comment forms, chat inputs, feedback dialogs, and email composers across virtually every modern web product. This snippet builds a complete feedback form with auto-grow behaviour, a live character counter with colour-coded thresholds, a star rating widget, and a success state — all in pure HTML, CSS, and JavaScript.

The auto-resize mechanism

The core auto-grow technique is a two-step height update in the resize() function. First, textarea.style.height = 'auto' collapses the element to its minimum size, allowing the browser to recalculate scrollHeight accurately. Second, textarea.style.height = textarea.scrollHeight + 'px' sets the height to the exact content height. Without the reset to auto first, scrollHeight returns the previously set explicit height rather than the natural content height — the element would only grow, never shrink when lines are deleted. The textarea also has overflow: hidden to prevent a scrollbar from briefly appearing during the resize, and resize: none to remove the browser's default drag handle since the element manages its own height.

Character counter with threshold colouring

The counter reads textarea.value.length and textarea.getAttribute('maxlength') on every input event to compute a ratio. Below 80% the counter is a muted grey. At 80% it switches to amber using .warn classes. At 100% it switches to red with .error classes. Both the textarea and the counter row receive these classes so the border and the count text change colour in sync. The counter row below the textarea shares the same border and mirrors the textarea's border-color through CSS class pairing, creating a visually unified component.

Connected border treatment

The textarea and its counter bar are two separate elements that appear as one by sharing a border: the textarea has border-radius: 10px 10px 0 0 with border-bottom: none, and the counter bar has border-radius: 0 0 10px 10px with border-top: none. Both have border: 1.5px solid #e2e8f0. When the textarea receives focus or a warning class, the sibling counter row needs its border to match — CSS handles this via adjacent-sibling selectors like .textarea:focus + .counter-row.

Star rating widget

Each star button has a numeric data-v attribute. mouseenter calls lightStars(n) to visually preview the rating by adding the lit class to all stars with data-v ≤ n. mouseleave resets to lightStars(rating) — the last committed rating. click commits the rating by updating the rating variable. This three-listener pattern (preview, reset, commit) is the canonical star rating implementation.

Accessible counter

The counter span has aria-live="polite" so screen readers announce the count periodically without interrupting typing. The textarea has aria-describedby pointing at both the hint text and the counter span, associating both with the field for assistive technologies.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Instead of guessing why the height gets reset before it gets remeasured, paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why resize() sets height to auto before reading scrollHeight, and why skipping that step would make the textarea grow but never shrink. The same assistant is useful for optimizing it — ask whether running resize() and updateCounter() on every single input event could cause layout thrashing in a very long form with many auto-resizing fields, and how you'd batch those reads and writes. It's also a good partner for extending the form: ask it to persist the draft to localStorage as the user types, swap the character counter for a word counter, or add a max-height with internal scrolling once the textarea gets too tall. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build an auto-growing feedback form in plain HTML, CSS, and JavaScript — no libraries, no ResizeObserver.

Requirements:
- A textarea with resize: none and overflow: hidden in CSS, paired with a JavaScript resize function that first sets the element's height to "auto", then immediately reads its scrollHeight and sets the height to that value in pixels — in that exact order, so the field both grows and shrinks correctly as content is added or removed.
- Call that resize function on every input event, and also once on initial page load so any pre-filled value sizes the field correctly from the start.
- A live character counter that reads the textarea's current length against its maxlength attribute, showing a neutral color under 80 percent of the limit, an amber warning color between 80 and 100 percent, and a red error color at or above 100 percent — with the textarea's own border changing to match the same three states.
- Visually connect the textarea and the counter bar beneath it into one unit: the textarea has rounded top corners and no bottom border, the counter bar has rounded bottom corners and no top border, and use CSS sibling selectors (not JavaScript) so the counter bar's border color updates automatically whenever the textarea gains a warning/error class or receives focus.
- A five-star rating widget built from button elements where hovering previews a rating by lighting stars up to the hovered one, moving the mouse away reverts to the last clicked (committed) rating, and clicking commits a new rating.
- On submit, require the textarea to be non-empty (trimmed), and if valid, hide the form and reveal a success panel with a "submit another" action that fully resets the form, the textarea height, the counter, and the star rating back to their initial state.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Type in the textareaThe textarea grows downward as you type — no scrollbar, no fixed height. Deleting lines shrinks it back to fit the remaining content.
  2. 2
    Watch the character counterThe counter below the textarea shows current/500 characters. At 400 characters (80%) the border and counter turn amber as a warning.
  3. 3
    Approach the limitAt 500 characters the textarea border and counter turn red and the maxlength attribute prevents further input.
  4. 4
    Click the star ratingHover to preview a rating — stars light up in yellow as you move the mouse. Click to commit the selection.
  5. 5
    Submit the formClick Submit to validate (message is required) and show the green success state. The form hides and a confirmation panel appears.
  6. 6
    Clear or resetClick Clear to empty the textarea and reset the counter without hiding the form. Click "Submit another" on the success screen to start over.

Real-world uses

Common Use Cases

Comment and feedback forms
The primary use case — replace any fixed textarea in a comment box. Pair with a toast notification to confirm submission.
Chat and messaging inputs
Grow the input as messages get longer, exactly like Slack and iMessage. Combine with a chat UI for the full conversation layout.
Issue and bug report forms
Long bug descriptions need room. Pair with a multi-step form for a structured report with title, steps to reproduce, and expected result.
Email composers
Auto-grow the body field in an email compose UI so the layout scales naturally with message length.
Code snippet inputs
Use with a monospace font for multi-line code paste fields. The auto-grow keeps the full snippet visible without an internal scroll.

Got questions?

Frequently Asked Questions

If you only set height to scrollHeight without resetting first, scrollHeight reflects the previously set explicit height rather than the natural content height. The element would grow on each keystroke but never shrink when content is deleted, because the fixed height always equals or exceeds the content.

Add max-height: 300px and overflow-y: auto to the textarea CSS. The auto-resize sets an explicit height up to that maximum, then overflow-y triggers the scrollbar when the content exceeds it.

Replace textarea.value.length with textarea.value.trim().split(/\s+/).filter(Boolean).length for word count. Update the max variable to your word limit and adjust the aria-label on the counter span.

Yes — the height calculation works in any container. The only issue is if the modal uses display:none before opening, which means scrollHeight reads as 0. Call resize() after the modal's open animation completes (e.g. in a transitionend handler).

On every input event, after resize() and updateCounter(), call localStorage.setItem("draft", textarea.value). On page load, read it back with const saved = localStorage.getItem("draft"); and set textarea.value = saved || ""; then call resize().

Yes. Click the React, Vue, Angular, or Tailwind export above the preview. The resize logic is a single handler — set the height to auto, then to scrollHeight: in React attach it to onChange with a useRef on the textarea; in Vue use @input with a template ref; in Angular use (input) with a ViewChild reference. Because the growth is driven by scrollHeight rather than a fixed row count, the behaviour is identical across every framework — only where you attach the listener changes. Remember to call resize once after mount (useEffect / onMounted / ngAfterViewInit) so a pre-filled value sizes correctly. The Tailwind export converts the field, counter, and focus styling into utility classes.