Textarea
Provides a multi-line text input field with support for labels, captions, validation, and styling.
First release: 1.0.0 Latest update: July 1, 2025 Current version: 1.0.0
Import
import { PlusTextarea } from '@plusui/react';import { PlusTextareaComponent } from '@plusui/angular';import { PlusTextarea } from '@plusui/vue';import PlusTextarea from '@plusui/svelte';<script src="https://cdn.jsdelivr.net/npm/@plusui/core"></script>Package
@plusui/react@plusui/angular@plusui/vue@plusui/svelte@plusui/coreDocs
Changelog
Textarea Changelog
Recent changes and updates for the textarea component
v1.0.0
Added Textarea component for multi-line input
- Resizable textarea with size controls
- Character count and limits
- Validation with error messages
- Auto-resize functionality
- Form integration
The Textarea (plus-textarea) component allows users to enter multi-line text. It includes features like labels, captions, validation states (error, required), character limits, and size variations.
Anatomy
Section titled “Anatomy”
Basic Example
Section titled “Basic Example”A simple textarea with a placeholder.
import { PlusTextarea } from '@plusui/react';
export default () => { const handleInput = (event: React.FormEvent<HTMLPlusTextareaElement>) => { console.log('Input value:', event.currentTarget.value); }; const handleChange = (event: React.FormEvent<HTMLPlusTextareaElement>) => { console.log('Final value:', event.currentTarget.value); };
return ( <PlusTextarea placeholder="Enter your message here..." onPlusInput={handleInput} onPlusChange={handleChange} /> );};import { Component } from '@angular/core';
@Component({ selector: 'app-textarea-basic', template: ` <plus-textarea placeholder="Enter your message here..." (plus-input)="handleInput($event)" (plus-change)="handleChange($event)" ></plus-textarea> `})export class TextareaBasicComponent { handleInput(event: any) { console.log('Input value:', event.target.value); } handleChange(event: any) { console.log('Final value:', event.target.value); }}<template> <plus-textarea placeholder="Enter your message here..." @plus-input="handleInput" @plus-change="handleChange" ></plus-textarea></template>
<script setup>const handleInput = (event) => { console.log('Input value:', event.target.value);};const handleChange = (event) => { console.log('Final value:', event.target.value);};</script><script> function handleInput(event) { console.log('Input value:', event.target.value); } function handleChange(event) { console.log('Final value:', event.target.value); }</script>
<plus-textarea placeholder="Enter your message here..." on:plus-input={handleInput} on:plus-change={handleChange}></plus-textarea><plus-textarea id="basic-textarea" placeholder="Enter your message here..."></plus-textarea>
<script> const textarea = document.querySelector('plus-textarea'); textarea.addEventListener('plus-input', (event) => { console.log('Input value:', event.target.value); }); textarea.addEventListener('plus-change', (event) => { console.log('Final value:', event.target.value); });</script>Label and Caption
Section titled “Label and Caption”Use the label prop for a visible label and caption for helper text below the textarea.
import { PlusTextarea } from '@plusui/react';
export default () => { return ( <> <PlusTextarea label="Your Feedback" caption="Please provide detailed feedback." fullWidth="true"> </PlusTextarea> </> );};import { Component } from '@angular/core';
@Component({ selector: 'app-example', template: ` <plus-textarea label="Your Feedback" caption="Please provide detailed feedback." full-width="true"> </plus-textarea> `})export class ExampleComponent {}<template> <plus-textarea label="Your Feedback" caption="Please provide detailed feedback." full-width="true"> </plus-textarea></template><plus-textarea label="Your Feedback" caption="Please provide detailed feedback." full-width="true"></plus-textarea><plus-textarea label="Your Feedback" caption="Please provide detailed feedback." full-width="true"></plus-textarea>Control the size of the textarea using the size prop.
import { PlusTextarea } from '@plusui/react';
export default () => { return ( <> <PlusTextarea size="sm" label="Small Textarea" placeholder="Small..." fullWidth="true"> </PlusTextarea> <PlusTextarea size="md" label="Medium Textarea" placeholder="Medium..." fullWidth="true"> </PlusTextarea> <PlusTextarea size="lg" label="Large Textarea" placeholder="Large..." fullWidth="true"> </PlusTextarea> </> );};import { Component } from '@angular/core';
@Component({ selector: 'app-example', template: ` <plus-textarea size="sm" label="Small Textarea" placeholder="Small..." full-width="true"> </plus-textarea> <plus-textarea size="md" label="Medium Textarea" placeholder="Medium..." full-width="true"> </plus-textarea> <plus-textarea size="lg" label="Large Textarea" placeholder="Large..." full-width="true"> </plus-textarea> `})export class ExampleComponent {}<template> <plus-textarea size="sm" label="Small Textarea" placeholder="Small..." full-width="true"> </plus-textarea> <plus-textarea size="md" label="Medium Textarea" placeholder="Medium..." full-width="true"> </plus-textarea> <plus-textarea size="lg" label="Large Textarea" placeholder="Large..." full-width="true"> </plus-textarea></template><plus-textarea size="sm" label="Small Textarea" placeholder="Small..." full-width="true"></plus-textarea><plus-textarea size="md" label="Medium Textarea" placeholder="Medium..." full-width="true"></plus-textarea><plus-textarea size="lg" label="Large Textarea" placeholder="Large..." full-width="true"></plus-textarea><plus-textarea size="sm" label="Small Textarea" placeholder="Small..." full-width="true"></plus-textarea><plus-textarea size="md" label="Medium Textarea" placeholder="Medium..." full-width="true"></plus-textarea><plus-textarea size="lg" label="Large Textarea" placeholder="Large..." full-width="true"></plus-textarea>Disabled and Readonly
Section titled “Disabled and Readonly”Use disabled to prevent interaction or readonly to prevent editing.
import { PlusTextarea } from '@plusui/react';
export default () => { return ( <> <PlusTextarea label="Disabled" value="Cannot edit" disabled fullWidth="true"> </PlusTextarea> <PlusTextarea label="Readonly" value="Cannot edit, but selectable" readonly fullWidth="true"> </PlusTextarea> </> );};import { Component } from '@angular/core';
@Component({ selector: 'app-example', template: ` <plus-textarea label="Disabled" value="Cannot edit" disabled full-width="true"> </plus-textarea> <plus-textarea label="Readonly" value="Cannot edit, but selectable" readonly full-width="true"> </plus-textarea> `})export class ExampleComponent {}<template> <plus-textarea label="Disabled" value="Cannot edit" disabled full-width="true"> </plus-textarea> <plus-textarea label="Readonly" value="Cannot edit, but selectable" readonly full-width="true"> </plus-textarea></template><plus-textarea label="Disabled" value="Cannot edit" disabled full-width="true"></plus-textarea><plus-textarea label="Readonly" value="Cannot edit, but selectable" readonly full-width="true"></plus-textarea><plus-textarea label="Disabled" value="Cannot edit" disabled full-width="true"></plus-textarea><plus-textarea label="Readonly" value="Cannot edit, but selectable" readonly full-width="true"></plus-textarea>Error State
Section titled “Error State”Set the error prop to indicate an invalid state. The caption will display the error message (either the default browser validation message, the error-message prop if provided, or the standard caption if no validation error exists).
import { PlusTextarea } from '@plusui/react';
export default () => { return ( <> <PlusTextarea label="Comment" required error="true" errorMessage="Please enter a comment." fullWidth="true"> </PlusTextarea> <PlusTextarea label="Optional Field" caption="This has a normal caption" error="true" fullWidth="true"> </PlusTextarea> </> );};import { Component } from '@angular/core';
@Component({ selector: 'app-example', template: ` <plus-textarea label="Comment" required error="true" error-message="Please enter a comment." full-width="true"> </plus-textarea> <plus-textarea label="Optional Field" caption="This has a normal caption" error="true" full-width="true"> </plus-textarea> `})export class ExampleComponent {}<template> <plus-textarea label="Comment" required error="true" error-message="Please enter a comment." full-width="true"> </plus-textarea> <plus-textarea label="Optional Field" caption="This has a normal caption" error="true" full-width="true"> </plus-textarea></template><plus-textarea label="Comment" required error="true" error-message="Please enter a comment." full-width="true"></plus-textarea><plus-textarea label="Optional Field" caption="This has a normal caption" error="true" full-width="true"></plus-textarea><plus-textarea label="Comment" required error="true" error-message="Please enter a comment." full-width="true"></plus-textarea><plus-textarea label="Optional Field" caption="This has a normal caption" error="true" full-width="true"></plus-textarea>Required Field
Section titled “Required Field”Mark the textarea as required using the required prop.
import { PlusTextarea } from '@plusui/react';
export default () => { const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); const formData = new FormData(e.target as HTMLFormElement); const feedback = formData.get('feedback'); if (!feedback) { console.log('Form invalid'); } else { console.log('Form submitted:', feedback); } };
return ( <form onSubmit={handleSubmit}> <PlusTextarea label="Required Feedback" required name="feedback" /> <button type="submit">Submit</button> </form> );};import { Component } from '@angular/core';
@Component({ selector: 'app-textarea-required', template: ` <form (ngSubmit)="handleSubmit()" #form="ngForm"> <plus-textarea label="Required Feedback" required name="feedback" [(ngModel)]="feedbackValue"></plus-textarea> <button type="submit">Submit</button> </form> `})export class TextareaRequiredComponent { feedbackValue = '';
handleSubmit() { if (!this.feedbackValue) { console.log('Form invalid'); } else { console.log('Form submitted:', this.feedbackValue); } }}<template> <form @submit="handleSubmit"> <plus-textarea label="Required Feedback" required name="feedback" v-model="feedbackValue"></plus-textarea> <button type="submit">Submit</button> </form></template>
<script setup>import { ref } from 'vue';
const feedbackValue = ref('');
const handleSubmit = (e) => { e.preventDefault(); if (!feedbackValue.value) { console.log('Form invalid'); } else { console.log('Form submitted:', feedbackValue.value); }};</script><script> let feedbackValue = '';
function handleSubmit(e) { e.preventDefault(); if (!feedbackValue) { console.log('Form invalid'); } else { console.log('Form submitted:', feedbackValue); } }</script>
<form on:submit={handleSubmit}> <plus-textarea label="Required Feedback" required name="feedback" bind:value={feedbackValue}></plus-textarea> <button type="submit">Submit</button></form><form id="req-form"> <plus-textarea label="Required Feedback" required name="feedback"></plus-textarea> <button type="submit">Submit</button></form>
<script> document.getElementById('req-form').addEventListener('submit', (e) => { const textarea = e.target.elements.feedback; if (!textarea.checkValidity()) { e.preventDefault(); textarea.reportValidity(); console.log('Form invalid'); } else { console.log('Form submitted'); e.preventDefault(); } });</script>Character Limits
Section titled “Character Limits”Use minlength and maxlength to enforce character counts.
import { PlusTextarea } from '@plusui/react';
export default () => { return ( <> <PlusTextarea label="Short Bio" minlength="10" maxlength="100" placeholder="Min 10, Max 100 chars" fullWidth="true"> </PlusTextarea> </> );};import { Component } from '@angular/core';
@Component({ selector: 'app-example', template: ` <plus-textarea label="Short Bio" minlength="10" maxlength="100" placeholder="Min 10, Max 100 chars" full-width="true"> </plus-textarea> `})export class ExampleComponent {}<template> <plus-textarea label="Short Bio" minlength="10" maxlength="100" placeholder="Min 10, Max 100 chars" full-width="true"> </plus-textarea></template><plus-textarea label="Short Bio" minlength="10" maxlength="100" placeholder="Min 10, Max 100 chars" full-width="true"></plus-textarea><plus-textarea label="Short Bio" minlength="10" maxlength="100" placeholder="Min 10, Max 100 chars" full-width="true"></plus-textarea>Set the initial visible number of lines using the rows attribute.
import { PlusTextarea } from '@plusui/react';
export default () => { return ( <> <PlusTextarea label="Address" rows="2" placeholder="Enter address..." fullWidth="true"> </PlusTextarea> </> );};import { Component } from '@angular/core';
@Component({ selector: 'app-example', template: ` <plus-textarea label="Address" rows="2" placeholder="Enter address..." full-width="true"> </plus-textarea> `})export class ExampleComponent {}<template> <plus-textarea label="Address" rows="2" placeholder="Enter address..." full-width="true"> </plus-textarea></template><plus-textarea label="Address" rows="2" placeholder="Enter address..." full-width="true"></plus-textarea><plus-textarea label="Address" rows="2" placeholder="Enter address..." full-width="true"></plus-textarea>Resize Control
Section titled “Resize Control”Control how the textarea can be resized using the resize prop.
import { PlusTextarea } from '@plusui/react';
export default () => { return ( <> <PlusTextarea resize="none" label="Resize: None" placeholder="Cannot resize"> </PlusTextarea> <PlusTextarea resize="vertical" label="Resize: Vertical (Default)" placeholder="Resize vertically"> </PlusTextarea> <PlusTextarea resize="horizontal" label="Resize: Horizontal" placeholder="Resize horizontally"> </PlusTextarea> <PlusTextarea resize="both" label="Resize: Both" placeholder="Resize both ways"> </PlusTextarea> </> );};import { Component } from '@angular/core';
@Component({ selector: 'app-example', template: ` <plus-textarea resize="none" label="Resize: None" placeholder="Cannot resize"> </plus-textarea> <plus-textarea resize="vertical" label="Resize: Vertical (Default)" placeholder="Resize vertically"> </plus-textarea> <plus-textarea resize="horizontal" label="Resize: Horizontal" placeholder="Resize horizontally"> </plus-textarea> <plus-textarea resize="both" label="Resize: Both" placeholder="Resize both ways"> </plus-textarea> `})export class ExampleComponent {}<template> <plus-textarea resize="none" label="Resize: None" placeholder="Cannot resize"> </plus-textarea> <plus-textarea resize="vertical" label="Resize: Vertical (Default)" placeholder="Resize vertically"> </plus-textarea> <plus-textarea resize="horizontal" label="Resize: Horizontal" placeholder="Resize horizontally"> </plus-textarea> <plus-textarea resize="both" label="Resize: Both" placeholder="Resize both ways"> </plus-textarea></template><plus-textarea resize="none" label="Resize: None" placeholder="Cannot resize"></plus-textarea><plus-textarea resize="vertical" label="Resize: Vertical (Default)" placeholder="Resize vertically"></plus-textarea><plus-textarea resize="horizontal" label="Resize: Horizontal" placeholder="Resize horizontally"></plus-textarea><plus-textarea resize="both" label="Resize: Both" placeholder="Resize both ways"></plus-textarea><plus-textarea resize="none" label="Resize: None" placeholder="Cannot resize"></plus-textarea><plus-textarea resize="vertical" label="Resize: Vertical (Default)" placeholder="Resize vertically"></plus-textarea><plus-textarea resize="horizontal" label="Resize: Horizontal" placeholder="Resize horizontally"></plus-textarea><plus-textarea resize="both" label="Resize: Both" placeholder="Resize both ways"></plus-textarea>Full Width
Section titled “Full Width”Allow the textarea to expand to the full width of its container using the full-width attribute.
import { PlusTextarea } from '@plusui/react';
export default () => { return ( <> <PlusTextarea label="Full Width Textarea" fullWidth="true" placeholder="Takes up full container width"> </PlusTextarea> </> );};import { Component } from '@angular/core';
@Component({ selector: 'app-example', template: ` <plus-textarea label="Full Width Textarea" full-width="true" placeholder="Takes up full container width"> </plus-textarea> `})export class ExampleComponent {}<template> <plus-textarea label="Full Width Textarea" full-width="true" placeholder="Takes up full container width"> </plus-textarea></template><plus-textarea label="Full Width Textarea" full-width="true" placeholder="Takes up full container width"></plus-textarea><plus-textarea label="Full Width Textarea" full-width="true" placeholder="Takes up full container width"></plus-textarea>Visual Sections
Section titled “Visual Sections”Layout & Spacing
Section titled “Layout & Spacing”
Light & Dark Mode
Section titled “Light & Dark Mode”
Accessibility (a11y)
Section titled “Accessibility (a11y)”- Keyboard Behavior: Standard textarea keyboard interactions apply.
- Screen Reader:
- The
labelprop provides an accessible name. aria-describedbylinks the textarea to the caption/error message when present.aria-invalidis set when theerrorprop is true.aria-requiredis set when therequiredprop is true.
- The
- Required Developer Actions:
- Always provide a meaningful
labelfor accessibility. - Use the
captionprop for hints or instructions. - Use the
erroranderror-messageprops appropriately to convey validation status.
- Always provide a meaningful
API Reference
Section titled “API Reference”Properties
Section titled “Properties”| Name | Type | Default | Description | Required |
|---|---|---|---|---|
name | string | '' | The native textarea name attribute. | No |
value | string | '' | The current value of the textarea. | No |
placeholder | string | '' | Placeholder text shown when the textarea is empty. | No |
size | 'sm' | 'md' | 'lg' | 'md' | The visual size of the textarea. | No |
disabled | boolean | false | Disables the textarea, preventing interaction and form submission. | No |
readonly | boolean | false | Makes the textarea read-only, preventing modification but allowing focus. | No |
required | boolean | false | Marks the textarea as required for form submission. | No |
label | string | undefined | The visible label text for the textarea. | No |
minlength | number | undefined | Minimum number of characters required. | No |
maxlength | number | undefined | Maximum number of characters allowed. | No |
autoFocus | boolean | undefined | Automatically focuses the textarea when the page loads. | No |
caption | string | undefined | Helper text displayed below the textarea. | No |
error | boolean | false | Puts the textarea in an error state, usually for validation feedback. | No |
errorMessage | string | '' | Custom error message to display when error is true. Overrides default. | No |
fullWidth | boolean | false | Allows the textarea to take the full width of its container. | No |
rows | number | 4 | The number of visible text lines for the textarea. | No |
resize | 'none' | 'vertical' | 'horizontal' | 'both' | 'vertical' | Controls how the textarea can be resized by the user. | No |
wrap | 'hard' | 'soft' | 'off' | 'soft' | Specifies how the text wraps when submitted in a form. | No |
Events
Section titled “Events”| Name | Payload Type | Description |
|---|---|---|
plus-input | Event | Fired immediately when the value changes. |
plus-change | Event | Fired when the value changes and the element loses focus. |
plus-focus | FocusEvent | Fired when the textarea gains focus. |
plus-blur | FocusEvent | Fired when the textarea loses focus. |
plus-invalid | CustomEvent<{ validationMessage: string }> | Fired when the textarea fails validation on form submission. |
This component does not utilize named slots.
Methods
Section titled “Methods”| Name | Parameters | Returns | Description |
|---|---|---|---|
checkValidity() | undefined | boolean | Checks if the current value satisfies validation constraints. |
reportValidity() | undefined | boolean | Checks validity and displays validation message if invalid. |
setCustomValidity() | message: string | void | Sets a custom validation message. Clears with empty string. |
CSS Shadow Parts
Section titled “CSS Shadow Parts”| Name | Description |
|---|---|
textarea | The native <textarea> element. |
label | The <label> element. |
caption | The <div> containing caption/error text. |
CSS Custom Properties
Section titled “CSS Custom Properties”| Name | Description |
|---|---|
--focus-ring-color | Color of the focus ring. |
--error-color | Color used for error states. |
Join the Community
Plus UI is built by the community. Join us on our platforms to contribute, get help, and stay up to date.