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.
- ✅ 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
npx ngsui-cli add textareanpm install angular-superuiThe 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 --helpimport { 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('');
}@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('');
}@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...');
}<TextareaComponent
placeholder="Standard textarea with border"
variant="default"
label="Default Textarea"
/><TextareaComponent
placeholder="Textarea with background fill"
variant="filled"
label="Filled Textarea"
/><TextareaComponent
placeholder="Minimal design with bottom border only"
variant="flushed"
label="Flushed Textarea"
/><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"
/><!-- 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" /><TextareaComponent
state="success"
[value]="successValue()"
(valueChange)="successValue.set($event)"
placeholder="Success textarea"
/><TextareaComponent
state="error"
[value]="errorValue()"
(valueChange)="errorValue.set($event)"
placeholder="Error textarea"
errorMessage="This field is required"
/><TextareaComponent
state="warning"
[value]="warningValue()"
(valueChange)="warningValue.set($event)"
placeholder="Warning textarea"
/><TextareaComponent
resize="none"
placeholder="Fixed size textarea"
helpText="Users cannot resize this textarea"
/><TextareaComponent
resize="vertical"
placeholder="Vertically resizable"
helpText="Users can resize vertically (grab bottom edge)"
/><TextareaComponent
resize="horizontal"
placeholder="Horizontally resizable"
helpText="Users can resize horizontally (grab right edge)"
/><TextareaComponent
resize="both"
placeholder="Fully resizable"
helpText="Users can resize in both directions (grab corner)"
/>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);
}
}
}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();
}
}
}@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;
};
}<TextareaComponent
[value]="autoResizeValue()"
(valueChange)="autoResizeValue.set($event)"
[autoResize]="true"
[minRows]="3"
[maxRows]="15"
placeholder="Constrained auto-resize between 3-15 rows"
/><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"
/><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"
/><TextareaComponent
[value]="requiredValue()"
(valueChange)="requiredValue.set($event)"
placeholder="This field is required"
[required]="true"
label="Required Textarea"
helpText="This field must be filled out"
/>| Property | Type | Default | Description |
|---|---|---|---|
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 |
placeholder |
string |
'' |
Placeholder text |
disabled |
boolean |
false |
Whether the textarea is disabled |
readonly |
boolean |
false |
Whether the textarea is read-only |
required |
boolean |
false |
Whether the textarea is required |
rows |
number |
4 |
Number of visible text lines |
cols |
number | undefined |
undefined |
Number of visible character widths |
minLength |
number | undefined |
undefined |
Minimum character length |
maxLength |
number | undefined |
undefined |
Maximum character length |
wrap |
'hard' | 'soft' | 'off' |
'soft' |
Text wrapping behavior |
className |
string |
'' |
Additional CSS classes |
id |
string |
'' |
Element ID (auto-generated if not provided) |
name |
string |
'' |
Form control name |
label |
string |
'' |
Label text |
ariaLabel |
string |
'' |
ARIA label for accessibility |
ariaLabelledBy |
string |
'' |
ARIA labelledby attribute |
ariaDescribedBy |
string |
'' |
ARIA describedby attribute |
errorMessage |
string |
'' |
Error message to display |
helpText |
string |
'' |
Help text to display |
autoResize |
boolean |
false |
Enable auto-resize functionality |
maxRows |
number |
10 |
Maximum rows for auto-resize |
minRows |
number |
2 |
Minimum rows for auto-resize |
showCharacterCount |
boolean |
false |
Show character counter |
validateOnChange |
boolean |
false |
Validate on value change |
validateOnBlur |
boolean |
true |
Validate on blur |
customValidator |
(value: string) => string | null |
undefined |
Custom validation function |
| Event | Type | Description |
|---|---|---|
valueChange |
string |
Emitted when the value changes |
textareaBlur |
FocusEvent |
Emitted when the textarea loses focus |
textareaFocus |
FocusEvent |
Emitted when the textarea gains focus |
keyDown |
KeyboardEvent |
Emitted on keydown events |
keyUp |
KeyboardEvent |
Emitted on keyup events |
validationChange |
boolean |
Emitted when validation state changes |
// Using model() signal for two-way binding
<TextareaComponent [(value)]="myValue" />
// Using separate input and output
<TextareaComponent
[value]="myValue()"
(valueChange)="myValue.set($event)"
/>| Method | Parameters | Description |
|---|---|---|
focus() |
none | Focus the textarea |
blur() |
none | Blur the textarea |
select() |
none | Select all text |
setSelectionRange() |
start: number, end: number |
Set text selection range |
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
The component automatically manages these ARIA attributes:
aria-label- Accessible namearia-labelledby- References to labeling elementsaria-describedby- References to describing elements (help text, errors)aria-invalid- Validation statearia-required- Required field indicationrole="alert"- Error message announcements
<TextareaComponent
className="my-custom-class border-2 border-blue-500 focus:border-blue-700"
placeholder="Custom styled textarea"
/>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');
}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
"
/>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
- Chrome 91+
- Firefox 90+
- Safari 14+
- Edge 91+
// 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' : ''"
/>// 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..."
/>For more examples and interactive demos, visit our Textarea Demo Page.
Contributions are welcome! Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.
This project is licensed under the MIT License - see the LICENSE file for details.