HTML <textarea> reference Jump to this section

Common attributes Jump to this section

Basic textarea structure Jump to this section

A <textarea> element creates a multi-line text input field. Unlike input elements, textareas can contain multiple lines of text and the content goes between opening and closing tags.

<textarea name="message" rows="4" cols="30">
Default text here
</textarea>

name Jump to this section

Identifies the textarea when submitting form data. The name becomes the key, and the textarea content becomes the data.

Use descriptive names that match your backend expectations (e.g., comment, message, description).

<textarea name="comment">User's comment</textarea>
<!-- Submits as: comment=User's comment -->

rows Jump to this section

Specifies the visible height of the textarea in lines of text. This is the initial height; users can typically resize it.

Use to set an appropriate starting size for the expected content length.

<textarea rows="6">
Taller textarea
</textarea>

cols Jump to this section

Specifies the visible width of the textarea in average character widths. Modern layouts often use CSS width instead.

Use when you need a specific character-based width, though CSS is generally more flexible.

<textarea cols="50">
Wider textarea
</textarea>

placeholder Jump to this section

Displays hint text when the textarea is empty. Disappears when the user starts typing.

Use to provide examples or instructions. Don't rely on it for critical information as it disappears.

<textarea placeholder="Tell us what you think...">
</textarea>

maxlength Jump to this section

Limits the maximum number of characters users can enter. The browser prevents additional input once reached.

Use to enforce character limits for comments, tweets, or database field constraints.

<textarea maxlength="100">
</textarea>

minlength Jump to this section

Requires a minimum number of characters for form validation. The form won't submit if the content is too short.

Use when you need substantive input (e.g., requiring at least 10 characters for a review).

<textarea minlength="10" required>
</textarea>

required Jump to this section

Boolean attribute that makes the textarea mandatory for form submission. The form won't submit if left empty.

Use on fields that must have content. Pair with clear labels.

<textarea required>
</textarea>

disabled Jump to this section

Boolean attribute that makes the textarea non-interactive and excludes it from form submission. Typically displayed with reduced opacity.

Use when the textarea is temporarily unavailable or depends on another action.

<textarea disabled>
Cannot edit this
</textarea>

readonly Jump to this section

Boolean attribute that makes the textarea non-editable but still allows selection and copying. Unlike disabled, readonly fields are included in form submission.

Use when you want to display text that users can copy but not modify.

<textarea readonly>
Read-only content
</textarea>

autofocus Jump to this section

Automatically focuses the textarea when the page loads. Only one element per page should have this attribute.

Use sparingly on forms where the textarea is the primary action. Can disorient users if overused.

<textarea autofocus>
</textarea>

autocomplete Jump to this section

Controls browser autofill behavior. Can specify what type of data the browser should suggest.

Common values: on, off, name, email, street-address, tel, etc.

<textarea name="address" autocomplete="street-address">
</textarea>

<textarea autocomplete="off">
    <!-- Disable autocomplete -->
</textarea>

spellcheck Jump to this section

Controls whether the browser should check spelling. Accepts true or false.

Use false for code editors, usernames, or technical content. Use true for prose.

<textarea spellcheck="true">
    Regular text with spell checking
</textarea>

<textarea spellcheck="false">
    code_without_spellcheck()
</textarea>

wrap Jump to this section

Controls how text is wrapped when submitting the form.

Values:

  • soft (default): Text wraps visually but newlines aren't added to submitted data
  • hard: Newlines are added to match visual wrapping (requires cols attribute)
<textarea wrap="soft">
    Text wraps visually only
</textarea>

<textarea wrap="hard" cols="40">
    Newlines added on submit
</textarea>

form Jump to this section

Associates the textarea with a form element by its id, even when the textarea is outside the form in the DOM.

<form id="feedback-form">
    <!-- other inputs -->
</form>

<textarea name="message" form="feedback-form">
</textarea>

autocapitalize Jump to this section

Controls automatic capitalization on mobile devices.

Values: off, none, on, sentences, words, characters

<textarea autocapitalize="sentences">
    First letter of sentences capitalized
</textarea>

<textarea autocapitalize="off">
    no automatic capitalization
</textarea>

autocorrect Jump to this section

Controls automatic text correction on iOS devices. Non-standard but widely used.

<textarea autocorrect="on">
    Auto-correction enabled
</textarea>

<textarea autocorrect="off">
    No auto-correction
</textarea>

dirname Jump to this section

Submits the text directionality (ltr or rtl) along with the content. The attribute value becomes the name for the direction data.

<textarea name="comment" dirname="comment.dir">
</textarea>
<!-- Submits: comment=text content&comment.dir=ltr -->

aria-label Jump to this section

Provides an accessible label for the textarea when a visible <label> element isn't present.

<textarea aria-label="Enter your feedback">
</textarea>

aria-describedby Jump to this section

References additional descriptive text by its id to provide context or instructions.

<textarea id="bio" aria-describedby="bio-help">
</textarea>
<small id="bio-help">Write a brief description about yourself</small>

aria-invalid Jump to this section

Indicates whether the textarea has a validation error. Should be updated dynamically with JavaScript.

<textarea aria-invalid="true" aria-describedby="error-msg">
</textarea>
<span id="error-msg">This field contains errors</span>

aria-required Jump to this section

Indicates that the field is required. Use in addition to the required attribute for better accessibility.

<textarea required aria-required="true">
</textarea>

data-* attributes Jump to this section

Custom attributes for storing application-specific data on the textarea element.

<textarea data-max-words="500" data-type="review">
</textarea>

<script>
  const textarea = document.querySelector('textarea');
  const maxWords = textarea.dataset.maxWords; // "500"
  const type = textarea.dataset.type; // "review"
</script>

id Jump to this section

Unique identifier for the textarea. Essential for linking with <label> elements and JavaScript selection.

<label for="message">Message:</label>
<textarea id="message" name="message">
</textarea>

class Jump to this section

Assigns CSS class names for styling or JavaScript selection.

<textarea class="form-control textarea-lg">
</textarea>

title Jump to this section

Provides a tooltip on hover. Not accessible to keyboard-only users or mobile, so don't rely on it for critical information.

<textarea title="Enter detailed feedback here">
</textarea>

tabindex Jump to this section

Controls keyboard focus order. Textarea elements are focusable by default, so rarely needed.

<textarea tabindex="1">
</textarea>

Styling basics Jump to this section

Textarea elements are highly customizable with CSS. Control appearance, resize behavior, and visual states to match your design system.

Basic textarea Jump to this section

.textarea-basic {
    padding: 10px 14px;
    border: 1px solid #d1d5db;
    border-radius: 6px;
    font-size: 15px;
    width: 100%;
    font-family: inherit;
    line-height: 1.5;
}

Resize control Jump to this section

Control how users can resize the textarea using the resize property.

/* Vertical only (most common) */
.textarea-vertical {
    resize: vertical;
}

/* Horizontal only */ .textarea-horizontal { resize: horizontal; }

/* Both directions */ .textarea-both { resize: both; }

/* No resize */ .textarea-fixed { resize: none; }

Filled textarea (material style) Jump to this section

.textarea-filled {
    padding: 12px 14px;
    background: #f3f4f6;
    border: none;
    border-bottom: 2px solid #9ca3af;
    border-radius: 6px 6px 0 0;
    font-size: 15px;
    resize: vertical;
}

Underline textarea Jump to this section

.textarea-underline {
    padding: 8px 0;
    background: transparent;
    border: none;
    border-bottom: 1px solid #d1d5db;
    font-size: 15px;
    resize: none;
}

Bordered with shadow Jump to this section

.textarea-shadow {
    padding: 12px 14px;
    border: 1px solid #e5e7eb;
    border-radius: 8px;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
    resize: vertical;
}

Size variants Jump to this section

.textarea-small {
    padding: 6px 10px;
    font-size: 13px;
    border-radius: 4px;
}

.textarea-medium { padding: 10px 14px; font-size: 15px; border-radius: 6px; }

.textarea-large { padding: 14px 18px; font-size: 17px; border-radius: 8px; }

Disabled state Jump to this section

.textarea-basic:disabled {
    background: #f3f4f6;
    border-color: #e5e7eb;
    color: #9ca3af;
    cursor: not-allowed;
}

Readonly state Jump to this section

.textarea-basic:read-only {
    background: #fafafa;
    border-color: #e5e7eb;
    cursor: default;
}

Error state Jump to this section

.textarea-error {
    border: 2px solid #dc2626;
    background: #fef2f2;
}

.textarea-error:focus { outline: 2px solid #fca5a5; outline-offset: 2px; }

Success state Jump to this section

.textarea-success {
    border: 2px solid #16a34a;
    background: #f0fdf4;
}

Code editor style Jump to this section

.textarea-code {
    padding: 12px;
    background: #1e1e1e;
    color: #d4d4d4;
    border: 1px solid #3e3e3e;
    border-radius: 6px;
    font-family: 'Courier New', monospace;
    font-size: 14px;
    line-height: 1.6;
}

Colored textarea Jump to this section

.textarea-colored {
    padding: 12px 14px;
    background: #3b82f6;
    color: white;
    border: none;
    border-radius: 8px;
}

.textarea-colored::placeholder { color: rgba(255, 255, 255, 0.7); }

Focus states Jump to this section

Always provide clear focus indicators for accessibility. Use :focus pseudo-class.

Blue outline on focus Jump to this section

.textarea-basic {
    border: 1px solid #d1d5db;
    transition: all 0.2s;
}

.textarea-basic:focus { border-color: #3b82f6; outline: 2px solid #93c5fd; outline-offset: 2px; }

Glow effect on focus Jump to this section

.textarea-glow {
    border: 1px solid #d1d5db;
    transition: all 0.2s;
}

.textarea-glow:focus { border-color: #8b5cf6; box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.1); outline: none; }

Auto-expanding textarea Jump to this section

Textarea that grows with content (requires JavaScript).

.textarea-auto {
    resize: none;
    overflow: hidden;
    min-height: 60px;
}
// See JavaScript tab for implementation

With character counter Jump to this section

0/200
.counter-wrapper {
    position: relative;
}

.char-counter { position: absolute; bottom: 8px; right: 12px; font-size: 12px; color: #6b7280; background: white; padding: 2px 6px; border-radius: 4px; }

JavaScript patterns Jump to this section

Textareas are commonly used for comments, messages, and long-form input. Use event listeners to enhance functionality and provide real-time feedback.

Getting and setting value Jump to this section

Access the current text content using the value property.

const textarea = document.getElementById('js-value');

// Get the current value const text = textarea.value;

// Set new value textarea.value = 'New text content';

// Append text textarea.value += '\nAdditional line';

// Clear textarea textarea.value = '';

Character counter Jump to this section

Display remaining or used characters in real-time.

0 / 150
const textarea = document.getElementById('js-counter');
const counter = document.getElementById('js-counter-display');
const maxLength = textarea.maxLength;
function updateCounter() {
    const length = textarea.value.length;
    counter.textContent = `${length} / ${maxLength}`;
    // Change color when near limit
    if (length > maxLength * 0.9) {
        counter.style.color = '#dc2626';
    } else if (length > maxLength * 0.7) {
        counter.style.color = '#f59e0b';
    } else {
        counter.style.color = '#6b7280';
    }
}
textarea.addEventListener('input', updateCounter);
updateCounter(); // Initial count

Auto-expanding textarea Jump to this section

Automatically adjust height based on content.

const textarea = document.getElementById('js-autoexpand');
function autoExpand() {
    // Reset height to auto to get correct scrollHeight
    textarea.style.height = 'auto';
    // Set height to scrollHeight
    textarea.style.height = textarea.scrollHeight + 'px';
}
textarea.addEventListener('input', autoExpand);
// Call on load if there's initial content
autoExpand();

Word counter Jump to this section

Count words instead of characters.

0 words, 0 characters
const textarea = document.getElementById('js-wordcount');
const wordDisplay = document.getElementById('word-count');
const charDisplay = document.getElementById('char-count');
function countWords() {
    const text = textarea.value.trim();
    // Count words (split by whitespace)
    const words = text === '' ? 0 : 
        text.split(/\s+/).length;
    // Count characters (excluding spaces)
    const chars = text.replace(/\s/g, '').length;
    wordDisplay.textContent = words;
    charDisplay.textContent = chars;
}
textarea.addEventListener('input', countWords);

Save draft automatically Jump to this section

Auto-save content to localStorage as the user types.

const textarea = document.getElementById('js-autosave');
const status = document.getElementById('js-autosave-status');
const storageKey = 'draft-content';
let saveTimeout;
// Restore saved draft
const saved = localStorage.getItem(storageKey);
if (saved) {
    textarea.value = saved;
    status.textContent = 'Draft restored';
}
// Auto-save with debounce
textarea.addEventListener('input', () => {
    clearTimeout(saveTimeout);
    status.textContent = 'Typing...';
    saveTimeout = setTimeout(() => {
        localStorage.setItem(storageKey, textarea.value);
        status.textContent = 'Draft saved ✓';
        setTimeout(() => {
            status.textContent = '';
        }, 2000);
    }, 1000); // Save 1 second after typing stops
});

Insert text at cursor Jump to this section

Insert text at the current cursor position.

function insertAtCursor(textareaId, text) {
    const textarea = document.getElementById(textareaId);
    const start = textarea.selectionStart;
    const end = textarea.selectionEnd;
    const value = textarea.value;
    // Insert text at cursor
    textarea.value = value.substring(0, start) + 
        text + 
        value.substring(end);
    // Move cursor after inserted text
    const newPos = start + text.length;
    textarea.setSelectionRange(newPos, newPos);
    textarea.focus();
}

Markdown preview toggle Jump to this section

Toggle between edit and preview modes.

const textarea = document.getElementById('js-markdown');
const preview = document.getElementById('js-preview');
function switchMode(mode) {
    const editTab = document.getElementById('edit-tab');
    const previewTab = document.getElementById('preview-tab');
    if (mode === 'edit') {
        textarea.style.display = 'block';
        preview.style.display = 'none';
        editTab.style.background = 'white';
        previewTab.style.background = 'transparent';
    } else {
        // Simple markdown rendering
        const html = textarea.value
            .replace(/\*\*(.*?)\*\*/g, '$1')
            .replace(/\*(.*?)\*/g, '$1')
            .replace(/^- (.+)$/gm, '
  • $1
  • ') .replace(/(
  • .*<\/li>)/s, '
      $&
    '); preview.innerHTML = html; textarea.style.display = 'none'; preview.style.display = 'block'; editTab.style.background = 'transparent'; previewTab.style.background = 'white'; } }
  • Tab key support Jump to this section

    Allow tab key to insert tabs instead of moving focus.

    Tab key inserts indentation
    const textarea = document.getElementById('js-tab');
    textarea.addEventListener('keydown', (e) => {
        if (e.key === 'Tab') {
            e.preventDefault(); // Prevent focus change
            const start = textarea.selectionStart;
            const end = textarea.selectionEnd;
            const value = textarea.value;
            // Insert tab character
            textarea.value = value.substring(0, start) + 
                '\t' + 
                value.substring(end);
            // Move cursor after tab
            textarea.selectionStart = start + 1;
            textarea.selectionEnd = start + 1;
        }
    });
    

    Validate on submit Jump to this section

    Check content before allowing form submission.

    const form = document.getElementById('js-validate-form');
    const textarea = document.getElementById('js-validate');
    const error = document.getElementById('js-validate-error');
    form.addEventListener('submit', (e) => {
        e.preventDefault();
        const text = textarea.value.trim();
        if (text.length === 0) {
            error.textContent = 'This field is required';
            textarea.style.borderColor = '#dc2626';
            return;
        }
        if (text.length < 10) {
            error.textContent = 'Please enter at least 10 characters';
            textarea.style.borderColor = '#dc2626';
            return;
        }
        error.textContent = '';
        textarea.style.borderColor = '#d1d5db';
        alert('Form submitted!');
    });
    

    textarea.addEventListener('input', () => { error.textContent = ''; textarea.style.borderColor = '#d1d5db'; });

    Copy to clipboard Jump to this section

    Add a button to copy textarea content.

    function copyToClipboard() {
        const textarea = document.getElementById('js-copy');
        const button = document.getElementById('copy-btn');
        // Select and copy text
        textarea.select();
        document.execCommand('copy');
        // Or use modern API
        // navigator.clipboard.writeText(textarea.value);
        // Update button
        const originalText = button.textContent;
        button.textContent = 'Copied!';
        button.style.background = '#16a34a';
        setTimeout(() => {
            button.textContent = originalText;
            button.style.background = '#3b82f6';
        }, 2000);
    }
    

    Disable resize on mobile Jump to this section

    Prevent resize handle on mobile devices while allowing it on desktop.

    textarea {
        resize: vertical;
    }
    /* Disable resize on mobile */
    @media (max-width: 768px) {
        textarea {
            resize: none;
        }
    }
    

    Limit lines of input Jump to this section

    Prevent users from entering more than a certain number of lines.

    Try entering more than 3 lines
    const textarea = document.getElementById('js-maxlines');
    const maxLines = 3;
    textarea.addEventListener('input', () => {
        const lines = textarea.value.split('\n');
        if (lines.length > maxLines) {
            // Remove extra lines
            textarea.value = lines.slice(0, maxLines).join('\n');
        }
    });
    textarea.addEventListener('keydown', (e) => {
        if (e.key === 'Enter') {
            const lines = textarea.value.split('\n');
            if (lines.length >= maxLines) {
                e.preventDefault();
            }
        }
    });
    

    Mention autocomplete Jump to this section

    Basic @ mention functionality.

    const textarea = document.getElementById('js-mention');
    const mentionList = document.getElementById('mention-list');
    const users = ['Alice', 'Bob', 'Charlie', 'Diana'];
    textarea.addEventListener('input', (e) => {
        const value = textarea.value;
        const cursorPos = textarea.selectionStart;
        const textBeforeCursor = value.substring(0, cursorPos);
        const match = textBeforeCursor.match(/@(\w*)$/);
        if (match) {
            const query = match[1].toLowerCase();
            const matches = users.filter(u => 
                u.toLowerCase().startsWith(query)
            );
            if (matches.length > 0) {
                mentionList.innerHTML = matches.map(user => 
                    `
    ${user}
    ` ).join(''); mentionList.style.display = 'block'; } else { mentionList.style.display = 'none'; } } else { mentionList.style.display = 'none'; } }); function insertMention(name) { const value = textarea.value; const cursorPos = textarea.selectionStart; const textBeforeCursor = value.substring(0, cursorPos); const textAfterCursor = value.substring(cursorPos); const newTextBefore = textBeforeCursor.replace(/@\w*$/, `@${name} `); textarea.value = newTextBefore + textAfterCursor; textarea.setSelectionRange(newTextBefore.length, newTextBefore.length); mentionList.style.display = 'none'; textarea.focus(); }