Textarea Component

August 8, 2025 · View on GitHub

A powerful, fully-featured textarea component built with Angular signals for optimal performance. The TextareaComponent provides multi-line text input with auto-resize functionality, validation, character counting, and complete accessibility support.

Features

  • Auto-resize functionality - Intelligently adjusts height based on content
  • Character counting - Optional character counter with limit enforcement
  • Multiple variants - Default, filled, flushed, and unstyled options
  • Size variants - Small, medium, large, and extra-large sizes
  • State management - Success, error, warning, and default states
  • Validation support - Built-in and custom validation with real-time feedback
  • Form integration - Full reactive forms support with ControlValueAccessor
  • Accessibility - WCAG 2.1 compliant with ARIA support
  • Responsive design - Mobile-first approach with touch-friendly interactions
  • Animation support - Smooth transitions and visual feedback
  • TypeScript support - Full type safety with Angular signals
  • Customizable - Extensive styling options with Tailwind CSS integration

Installation

npx ngsui-cli add textarea

Manual Installation

npm install angular-superui

CLI Usage

The ngsui-cli provides convenient commands for working with components:

# Add textarea component to your project
npx ngsui-cli add textarea

# List all available components
npx ngsui-cli list

# Initialize SuperUI in a new project
npx ngsui-cli init

# Get help for any command
npx ngsui-cli --help

Basic Usage

Simple Textarea

import { TextareaComponent } from 'angular-superui';

@Component({
  template: `
    <TextareaComponent
      placeholder="Type your message here..."
      label="Message"
      [value]="messageValue()"
      (valueChange)="messageValue.set($event)"
    />
  `
})
export class MyComponent {
  readonly messageValue = signal('');
}

With Character Counting

@Component({
  template: `
    <TextareaComponent
      [value]="tweetValue()"
      (valueChange)="tweetValue.set($event)"
      placeholder="What's happening?"
      [maxLength]="280"
      [showCharacterCount]="true"
      helpText="Share your thoughts in 280 characters or less"
    />
  `
})
export class MyComponent {
  readonly tweetValue = signal('');
}

Auto-Resizing Textarea

@Component({
  template: `
    <TextareaComponent
      [value]="autoResizeValue()"
      (valueChange)="autoResizeValue.set($event)"
      placeholder="Start typing... This textarea will grow as you add content!"
      [autoResize]="true"
      [minRows]="3"
      [maxRows]="10"
      helpText="Automatically resizes between 3-10 rows based on content"
    />
  `
})
export class MyComponent {
  readonly autoResizeValue = signal('Try typing multiple lines of text here...');
}

Variants

Default Variant

<TextareaComponent
  placeholder="Standard textarea with border"
  variant="default"
  label="Default Textarea"
/>

Filled Variant

<TextareaComponent
  placeholder="Textarea with background fill"
  variant="filled"
  label="Filled Textarea"
/>

Flushed Variant

<TextareaComponent
  placeholder="Minimal design with bottom border only"
  variant="flushed"
  label="Flushed Textarea"
/>

Unstyled Variant

<TextareaComponent
  placeholder="Completely unstyled for custom design"
  variant="unstyled"
  label="Custom Textarea"
  className="border-2 border-dashed border-gray-300 rounded-lg p-3 bg-gray-50"
/>

Size Variants

<!-- Small -->
<TextareaComponent size="sm" placeholder="Compact textarea" />

<!-- Medium (Default) -->
<TextareaComponent size="md" placeholder="Standard textarea" />

<!-- Large -->
<TextareaComponent size="lg" placeholder="Spacious textarea" />

<!-- Extra Large -->
<TextareaComponent size="xl" placeholder="Extra large textarea" />

State Variants

Success State

<TextareaComponent
  state="success"
  [value]="successValue()"
  (valueChange)="successValue.set($event)"
  placeholder="Success textarea"
/>

Error State

<TextareaComponent
  state="error"
  [value]="errorValue()"
  (valueChange)="errorValue.set($event)"
  placeholder="Error textarea"
  errorMessage="This field is required"
/>

Warning State

<TextareaComponent
  state="warning"
  [value]="warningValue()"
  (valueChange)="warningValue.set($event)"
  placeholder="Warning textarea"
/>

Resize Options

No Resize

<TextareaComponent
  resize="none"
  placeholder="Fixed size textarea"
  helpText="Users cannot resize this textarea"
/>

Vertical Resize (Default)

<TextareaComponent
  resize="vertical"
  placeholder="Vertically resizable"
  helpText="Users can resize vertically (grab bottom edge)"
/>

Horizontal Resize

<TextareaComponent
  resize="horizontal"
  placeholder="Horizontally resizable"
  helpText="Users can resize horizontally (grab right edge)"
/>

Both Directions

<TextareaComponent
  resize="both"
  placeholder="Fully resizable"
  helpText="Users can resize in both directions (grab corner)"
/>

Form Integration

Reactive Forms

import { FormBuilder, Validators } from '@angular/forms';

@Component({
  template: `
    <form [formGroup]="contactForm" (ngSubmit)="onSubmit()">
      <TextareaComponent
        formControlName="subject"
        placeholder="Brief subject line"
        [rows]="2"
        [maxLength]="100"
        [showCharacterCount]="true"
        size="sm"
      />

      <TextareaComponent
        formControlName="message"
        placeholder="Your detailed message here..."
        [autoResize]="true"
        [minRows]="4"
        [maxRows]="10"
        [maxLength]="1000"
        [showCharacterCount]="true"
        helpText="Please provide as much detail as possible"
      />

      <button type="submit" [disabled]="!contactForm.valid">
        Submit Form
      </button>
    </form>
  `
})
export class ContactComponent {
  contactForm = this.fb.group({
    subject: ['', [Validators.required, Validators.maxLength(100)]],
    message: ['', [Validators.required, Validators.minLength(10), Validators.maxLength(1000)]]
  });

  constructor(private fb: FormBuilder) {}

  onSubmit(): void {
    if (this.contactForm.valid) {
      console.log('Form submitted:', this.contactForm.value);
    }
  }
}

FormControl Disabled State

The component properly handles both input-based and FormControl-based disabled states:

@Component({
  template: `
    <TextareaComponent
      formControlName="disableTest"
      placeholder="This demonstrates FormControl.disable() integration"
      helpText="This textarea can be disabled via FormControl.disable()"
    />
    
    <button (click)="toggleDisabled()">
      {{ isDisabled ? 'Enable' : 'Disable' }} Control
    </button>
  `
})
export class DisableExampleComponent {
  form = this.fb.group({
    disableTest: ['Test content']
  });

  get isDisabled() {
    return this.form.get('disableTest')?.disabled ?? false;
  }

  toggleDisabled(): void {
    const control = this.form.get('disableTest');
    if (control?.disabled) {
      control.enable();
    } else {
      control?.disable();
    }
  }
}

Custom Validation

@Component({
  template: `
    <TextareaComponent
      [value]="customValidationValue()"
      (valueChange)="customValidationValue.set($event)"
      placeholder="Type 'hello' to see success state"
      [customValidator]="customValidator"
      [validateOnChange]="true"
      helpText="Custom validation: must contain 'hello'"
    />
  `
})
export class CustomValidationComponent {
  readonly customValidationValue = signal('');

  customValidator = (value: string): string | null => {
    if (!value) return null;
    if (!value.toLowerCase().includes('hello')) {
      return 'Value must contain "hello"';
    }
    return null;
  };
}

Advanced Features

Auto-Resize with Constraints

<TextareaComponent
  [value]="autoResizeValue()"
  (valueChange)="autoResizeValue.set($event)"
  [autoResize]="true"
  [minRows]="3"
  [maxRows]="15"
  placeholder="Constrained auto-resize between 3-15 rows"
/>

Read-only Mode

<TextareaComponent
  value="This is a read-only textarea. You cannot edit this content, but you can select and copy it."
  [readonly]="true"
  helpText="Content is read-only but selectable"
/>

Word Wrap Control

<TextareaComponent
  [value]="wordWrapValue()"
  (valueChange)="wordWrapValue.set($event)"
  placeholder="Type a very long line to see how word wrapping behaves..."
  wrap="hard"
  [cols]="40"
  helpText="Hard wrap - line breaks are preserved when submitting"
/>

Required Field

<TextareaComponent
  [value]="requiredValue()"
  (valueChange)="requiredValue.set($event)"
  placeholder="This field is required"
  [required]="true"
  label="Required Textarea"
  helpText="This field must be filled out"
/>

API Reference

Input Properties

PropertyTypeDefaultDescription
variant'default' | 'filled' | 'flushed' | 'unstyled''default'Visual style variant
size'sm' | 'md' | 'lg' | 'xl''md'Size of the textarea
state'default' | 'error' | 'success' | 'warning''default'Visual state
resize'none' | 'vertical' | 'horizontal' | 'both''vertical'Resize behavior
placeholderstring''Placeholder text
disabledbooleanfalseWhether the textarea is disabled
readonlybooleanfalseWhether the textarea is read-only
requiredbooleanfalseWhether the textarea is required
rowsnumber4Number of visible text lines
colsnumber | undefinedundefinedNumber of visible character widths
minLengthnumber | undefinedundefinedMinimum character length
maxLengthnumber | undefinedundefinedMaximum character length
wrap'hard' | 'soft' | 'off''soft'Text wrapping behavior
classNamestring''Additional CSS classes
idstring''Element ID (auto-generated if not provided)
namestring''Form control name
labelstring''Label text
ariaLabelstring''ARIA label for accessibility
ariaLabelledBystring''ARIA labelledby attribute
ariaDescribedBystring''ARIA describedby attribute
errorMessagestring''Error message to display
helpTextstring''Help text to display
autoResizebooleanfalseEnable auto-resize functionality
maxRowsnumber10Maximum rows for auto-resize
minRowsnumber2Minimum rows for auto-resize
showCharacterCountbooleanfalseShow character counter
validateOnChangebooleanfalseValidate on value change
validateOnBlurbooleantrueValidate on blur
customValidator(value: string) => string | nullundefinedCustom validation function

Output Events

EventTypeDescription
valueChangestringEmitted when the value changes
textareaBlurFocusEventEmitted when the textarea loses focus
textareaFocusFocusEventEmitted when the textarea gains focus
keyDownKeyboardEventEmitted on keydown events
keyUpKeyboardEventEmitted on keyup events
validationChangebooleanEmitted when validation state changes

Two-way Binding

// Using model() signal for two-way binding
<TextareaComponent [(value)]="myValue" />

// Using separate input and output
<TextareaComponent
  [value]="myValue()"
  (valueChange)="myValue.set($event)"
/>

Public Methods

MethodParametersDescription
focus()noneFocus the textarea
blur()noneBlur the textarea
select()noneSelect all text
setSelectionRange()start: number, end: numberSet text selection range

Accessibility Features

The TextareaComponent is fully accessible and WCAG 2.1 compliant:

  • Keyboard Navigation: Full keyboard support with proper tab order
  • Screen Reader Support: Comprehensive ARIA attributes and announcements
  • Focus Management: Clear focus indicators and logical focus flow
  • Error Handling: Proper error announcements and validation feedback
  • Auto-resize Announcements: Screen reader notifications for auto-resize behavior

ARIA Attributes

The component automatically manages these ARIA attributes:

  • aria-label - Accessible name
  • aria-labelledby - References to labeling elements
  • aria-describedby - References to describing elements (help text, errors)
  • aria-invalid - Validation state
  • aria-required - Required field indication
  • role="alert" - Error message announcements

Styling and Customization

Custom Classes

<TextareaComponent
  className="my-custom-class border-2 border-blue-500 focus:border-blue-700"
  placeholder="Custom styled textarea"
/>

CSS Variables

The component uses CSS custom properties that can be overridden:

.my-textarea {
  --input: theme('colors.gray.300');
  --ring: theme('colors.blue.500');
  --destructive: theme('colors.red.500');
  --success: theme('colors.green.500');
  --warning: theme('colors.yellow.500');
}

Tailwind CSS Integration

The component is built with Tailwind CSS and supports all utility classes:

<TextareaComponent
  className="
    bg-gradient-to-r from-blue-50 to-purple-50
    border-2 border-blue-200
    focus:border-blue-500 focus:ring-blue-500
    rounded-xl shadow-lg
    transition-all duration-300
  "
/>

Performance Considerations

The TextareaComponent is optimized for performance:

  • Signal-based Architecture: Uses Angular signals for optimal change detection
  • OnPush Change Detection: Minimal re-renders with OnPush strategy
  • Computed Properties: Efficient reactive calculations
  • Debounced Validation: Optional debouncing for expensive validation operations
  • Memory Management: Proper cleanup of observers and event listeners

Browser Support

  • Chrome 91+
  • Firefox 90+
  • Safari 14+
  • Edge 91+

Migration Guide

From Angular Material TextArea

// Before (Angular Material)
<mat-form-field>
  <mat-label>Message</mat-label>
  <textarea matInput [(ngModel)]="message" placeholder="Type here..."></textarea>
  <mat-hint>Enter your message</mat-hint>
  <mat-error *ngIf="hasError">This field is required</mat-error>
</mat-form-field>

// After (Angular SuperUI)
<TextareaComponent
  [(value)]="message"
  label="Message"
  placeholder="Type here..."
  helpText="Enter your message"
  [errorMessage]="hasError ? 'This field is required' : ''"
/>

From Native Textarea

// Before (Native)
<label for="message">Message</label>
<textarea
  id="message"
  [(ngModel)]="message"
  placeholder="Type here..."
  class="form-control"
></textarea>

// After (Angular SuperUI)
<TextareaComponent
  [(value)]="message"
  label="Message"
  placeholder="Type here..."
/>

Examples and Demos

For more examples and interactive demos, visit our Textarea Demo Page.

Contributing

Contributions are welcome! Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.

License

This project is licensed under the MIT License - see the LICENSE file for details.