Textarea

Multi-line text input components for property descriptions, notes, and messages with character counting.

Textarea Variants

Standard Textarea

Textarea with Character Count

0/200

Auto-resize Textarea

Textarea States

✓ Description meets all requirements
Minimum 50 characters required

Textarea Sizes

Real Estate Use Cases

Property Listing Form

Client Message Form

0/500
Include your preferred viewing times

Implementation Guide

HTML Structure

<!-- Standard Textarea -->
<div class="lyd-input-group">
    <label class="lyd-input-label">Description</label>
    <textarea class="lyd-textarea" placeholder="Enter text..." rows="4"></textarea>
</div>

<!-- Textarea with Character Count -->
<div class="lyd-input-group">
    <label class="lyd-input-label">Message</label>
    <div class="lyd-textarea-wrapper">
        <textarea class="lyd-textarea" id="textarea" maxlength="200"></textarea>
        <span class="lyd-character-count" id="count">0/200</span>
    </div>
</div>

<!-- Textarea with Helper Text -->
<div class="lyd-input-group">
    <label class="lyd-input-label">Notes</label>
    <textarea class="lyd-textarea error" rows="3"></textarea>
    <div class="lyd-textarea-helper error">This field is required</div>
</div>

JavaScript Features

// Character Counter
function setupCharacterCounter(textareaId, counterId) {
    const textarea = document.getElementById(textareaId);
    const counter = document.getElementById(counterId);
    const maxLength = textarea.getAttribute('maxlength');
    
    function updateCount() {
        const current = textarea.value.length;
        counter.textContent = `${current}/${maxLength}`;
        
        // Update counter color
        counter.classList.remove('warning', 'error');
        if (current > maxLength * 0.9) {
            counter.classList.add('error');
        } else if (current > maxLength * 0.7) {
            counter.classList.add('warning');
        }
    }
    
    textarea.addEventListener('input', updateCount);
    updateCount();
}

// Auto-resize
function setupAutoResize(textarea) {
    function resize() {
        textarea.style.height = 'auto';
        textarea.style.height = textarea.scrollHeight + 'px';
    }
    
    textarea.addEventListener('input', resize);
    resize();
}

// Initialize
document.addEventListener('DOMContentLoaded', () => {
    // Character counters
    setupCharacterCounter('textarea-count', 'char-count');
    setupCharacterCounter('message-area', 'message-count');
    
    // Auto-resize
    const autoResize = document.getElementById('auto-resize');
    if (autoResize) {
        setupAutoResize(autoResize);
    }
});

React/Next.js Component

// components/Textarea.tsx
import { TextareaHTMLAttributes, forwardRef, useState, useEffect, useRef } from 'react';

interface TextareaProps extends TextareaHTMLAttributes {
  label?: string;
  helperText?: string;
  error?: boolean;
  success?: boolean;
  showCount?: boolean;
  autoResize?: boolean;
}

export const Textarea = forwardRef(({
  label,
  helperText,
  error = false,
  success = false,
  showCount = false,
  autoResize = false,
  maxLength,
  className = '',
  onChange,
  ...props
}, ref) => {
  const [count, setCount] = useState(0);
  const textareaRef = useRef(null);
  
  const handleChange = (e: React.ChangeEvent) => {
    setCount(e.target.value.length);
    
    if (autoResize && textareaRef.current) {
      textareaRef.current.style.height = 'auto';
      textareaRef.current.style.height = textareaRef.current.scrollHeight + 'px';
    }
    
    onChange?.(e);
  };
  
  useEffect(() => {
    if (autoResize && textareaRef.current) {
      textareaRef.current.style.height = 'auto';
      textareaRef.current.style.height = textareaRef.current.scrollHeight + 'px';
    }
  }, [autoResize]);
  
  const classes = `lyd-textarea ${error ? 'error' : ''} ${success ? 'success' : ''} ${autoResize ? 'auto-resize' : ''} ${className}`;
  
  return (
    <div className="lyd-input-group">
      {label && <label className="lyd-input-label">{label}</label>}
      {showCount && maxLength ? (
        <div className="lyd-textarea-wrapper">
          <textarea
            ref={ref || textareaRef}
            className={classes}
            maxLength={maxLength}
            onChange={handleChange}
            {...props}
          />
          <span className={`lyd-character-count ${count > maxLength * 0.9 ? 'error' : count > maxLength * 0.7 ? 'warning' : ''}`}>
            {count}/{maxLength}
          </span>
        </div>
      ) : (
        <textarea
          ref={ref || textareaRef}
          className={classes}
          onChange={handleChange}
          {...props}
        />
      )}
      {helperText && (
        <div className={`lyd-textarea-helper ${error ? 'error' : ''} ${success ? 'success' : ''}`}>
          {helperText}
        </div>
      )}
    </div>
  );
});

API Reference

Class Description
.lyd-textarea Base textarea class
.lyd-textarea-wrapper Container for textarea with character count
.lyd-character-count Character counter display
.lyd-textarea-helper Helper text below textarea
.auto-resize Auto-resizing textarea
.small Small size variant
.large Large size variant
.error Error state styling
.success Success state styling

Accessibility & Best Practices

Accessibility Features

  • Use proper label associations
  • Provide clear placeholder text
  • Include aria-describedby for helper text
  • Announce character count changes
  • Ensure keyboard navigation

Best Practices

  • Set appropriate default rows
  • Allow vertical resizing only
  • Provide character limits when needed
  • Use helper text for guidance
  • Consider auto-save for long content
  • Validate content appropriately