Data input
Textarea
A native multiline text field with a label, help text, an error message and optional auto resize.
| Class name | Type | Description |
|---|---|---|
textareaplf:textarea | Component | A native multiline text field |
textarea-autoplf:textarea-auto | Behavior | Grows with its text |
textarea-smplf:textarea-sm | Size | Small |
textarea-lgplf:textarea-lg | Size | Large |
Default
Use the same label and help text as Input, and put the initial text between the tags, not in a value attribute.
Line breaks remain in the value. Text can wrap without adding a line break.
<div class="field">
<label class="field-label" for="textarea-bio">Biography</label>
<textarea class="textarea" id="textarea-bio" name="biography" aria-describedby="textarea-bio-help" placeholder="A short introduction">Building for plain HTML.</textarea>
<p class="field-help" id="textarea-bio-help">Line breaks remain in the value. Text can wrap without adding a line break.</p>
</div><div class="plf:field">
<label class="plf:field-label" for="textarea-bio">Biography</label>
<textarea class="plf:textarea" id="textarea-bio" name="biography" aria-describedby="textarea-bio-help" placeholder="A short introduction">Building for plain HTML.</textarea>
<p class="plf:field-help" id="textarea-bio-help">Line breaks remain in the value. Text can wrap without adding a line break.</p>
</div>import { Field, Textarea } from '@pelatform/react';
export function Example() {
return (
<Field label="Biography" help="Line breaks remain in the value. Text can wrap without adding a line break.">
<Textarea name="biography" placeholder="A short introduction" defaultValue="Building for plain HTML." />
</Field>
);
}import { Field, Textarea } from '@pelatform/react/prefixed';
export function Example() {
return (
<Field label="Biography" help="Line breaks remain in the value. Text can wrap without adding a line break.">
<Textarea name="biography" placeholder="A short introduction" defaultValue="Building for plain HTML." />
</Field>
);
}Invalid
Your application adds and removes aria-invalid and the error message; typing and a form reset do not clear them.
Introduce your work in a few sentences.
Error: add an introduction before continuing.
<div class="field">
<label class="field-label" for="textarea-intro">Required introduction <span class="field-required">(required)</span></label>
<textarea class="textarea" id="textarea-intro" name="introduction" required aria-invalid="true" aria-describedby="textarea-intro-help textarea-intro-error" placeholder="Add an introduction"></textarea>
<p class="field-help" id="textarea-intro-help">Introduce your work in a few sentences.</p>
<p class="field-error" id="textarea-intro-error">Error: add an introduction before continuing.</p>
</div><div class="plf:field">
<label class="plf:field-label" for="textarea-intro">Required introduction <span class="plf:field-required">(required)</span></label>
<textarea class="plf:textarea" id="textarea-intro" name="introduction" required aria-invalid="true" aria-describedby="textarea-intro-help textarea-intro-error" placeholder="Add an introduction"></textarea>
<p class="plf:field-help" id="textarea-intro-help">Introduce your work in a few sentences.</p>
<p class="plf:field-error" id="textarea-intro-error">Error: add an introduction before continuing.</p>
</div>import { Field, Textarea } from '@pelatform/react';
export function Example() {
return (
<Field label="Required introduction" help="Introduce your work in a few sentences." error="Error: add an introduction before continuing." required>
<Textarea name="introduction" placeholder="Add an introduction" />
</Field>
);
}import { Field, Textarea } from '@pelatform/react/prefixed';
export function Example() {
return (
<Field label="Required introduction" help="Introduce your work in a few sentences." error="Error: add an introduction before continuing." required>
<Textarea name="introduction" placeholder="Add an introduction" />
</Field>
);
}Focusable and included in form data.
<div class="field">
<label class="field-label" for="textarea-readonly">Readonly notes</label>
<textarea class="textarea" id="textarea-readonly" name="notes" readonly aria-describedby="textarea-readonly-help">These notes are available to read and copy.</textarea>
<p class="field-help" id="textarea-readonly-help">Focusable and included in form data.</p>
</div><div class="plf:field">
<label class="plf:field-label" for="textarea-readonly">Readonly notes</label>
<textarea class="plf:textarea" id="textarea-readonly" name="notes" readonly aria-describedby="textarea-readonly-help">These notes are available to read and copy.</textarea>
<p class="plf:field-help" id="textarea-readonly-help">Focusable and included in form data.</p>
</div>import { Field, Textarea } from '@pelatform/react';
export function Example() {
return (
<Field label="Readonly notes" help="Focusable and included in form data.">
<Textarea name="notes" readOnly defaultValue="These notes are available to read and copy." />
</Field>
);
}import { Field, Textarea } from '@pelatform/react/prefixed';
export function Example() {
return (
<Field label="Readonly notes" help="Focusable and included in form data.">
<Textarea name="notes" readOnly defaultValue="These notes are available to read and copy." />
</Field>
);
}Unavailable values are excluded from form data.
<div class="field">
<label class="field-label" for="textarea-disabled">Disabled notes</label>
<textarea class="textarea" id="textarea-disabled" name="disabled-notes" disabled aria-describedby="textarea-disabled-help">Editing is unavailable.</textarea>
<p class="field-help" id="textarea-disabled-help">Unavailable values are excluded from form data.</p>
</div><div class="plf:field">
<label class="plf:field-label" for="textarea-disabled">Disabled notes</label>
<textarea class="plf:textarea" id="textarea-disabled" name="disabled-notes" disabled aria-describedby="textarea-disabled-help">Editing is unavailable.</textarea>
<p class="plf:field-help" id="textarea-disabled-help">Unavailable values are excluded from form data.</p>
</div>import { Field, Textarea } from '@pelatform/react';
export function Example() {
return (
<Field label="Disabled notes" help="Unavailable values are excluded from form data.">
<Textarea name="disabled-notes" disabled defaultValue="Editing is unavailable." />
</Field>
);
}import { Field, Textarea } from '@pelatform/react/prefixed';
export function Example() {
return (
<Field label="Disabled notes" help="Unavailable values are excluded from form data.">
<Textarea name="disabled-notes" disabled defaultValue="Editing is unavailable." />
</Field>
);
}<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="field">
<label class="field-label" for="textarea-small">Small</label>
<textarea class="textarea textarea-sm" id="textarea-small" placeholder="A short note"></textarea>
</div>
<div class="field">
<label class="field-label" for="textarea-default">Default</label>
<textarea class="textarea" id="textarea-default" placeholder="A short note"></textarea>
</div>
<div class="field">
<label class="field-label" for="textarea-large">Large</label>
<textarea class="textarea textarea-lg" id="textarea-large" placeholder="A short note"></textarea>
</div>
</div><div class="plf:grid plf:grid-cols-1 plf:md:grid-cols-3 plf:gap-4">
<div class="plf:field">
<label class="plf:field-label" for="textarea-small">Small</label>
<textarea class="plf:textarea plf:textarea-sm" id="textarea-small" placeholder="A short note"></textarea>
</div>
<div class="plf:field">
<label class="plf:field-label" for="textarea-default">Default</label>
<textarea class="plf:textarea" id="textarea-default" placeholder="A short note"></textarea>
</div>
<div class="plf:field">
<label class="plf:field-label" for="textarea-large">Large</label>
<textarea class="plf:textarea plf:textarea-lg" id="textarea-large" placeholder="A short note"></textarea>
</div>
</div>import { Field, Textarea } from '@pelatform/react';
export function Example() {
return (
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<Field label="Small">
<Textarea size="sm" placeholder="A short note" />
</Field>
<Field label="Default">
<Textarea placeholder="A short note" />
</Field>
<Field label="Large">
<Textarea size="lg" placeholder="A short note" />
</Field>
</div>
);
}import { Field, Textarea } from '@pelatform/react/prefixed';
export function Example() {
return (
<div className="plf:grid plf:grid-cols-1 plf:md:grid-cols-3 plf:gap-4">
<Field label="Small">
<Textarea size="sm" placeholder="A short note" />
</Field>
<Field label="Default">
<Textarea placeholder="A short note" />
</Field>
<Field label="Large">
<Textarea size="lg" placeholder="A short note" />
</Field>
</div>
);
}Rows
rows never sets a height below the size’s minimum, and the handle resizes the height only.
Six rows set the initial height above the minimum. Direction follows the text; labels follow the page.
<div class="field">
<label class="field-label" for="textarea-notes">Workspace notes</label>
<textarea class="textarea" id="textarea-notes" rows="6" dir="auto" aria-describedby="textarea-notes-help">Write a clear first paragraph.
Keep the original line breaks.
https://example.test/workspaces/international-customer-success</textarea>
<p class="field-help" id="textarea-notes-help">Six rows set the initial height above the minimum. Direction follows the text; labels follow the page.</p>
</div><div class="plf:field">
<label class="plf:field-label" for="textarea-notes">Workspace notes</label>
<textarea class="plf:textarea" id="textarea-notes" rows="6" dir="auto" aria-describedby="textarea-notes-help">Write a clear first paragraph.
Keep the original line breaks.
https://example.test/workspaces/international-customer-success</textarea>
<p class="plf:field-help" id="textarea-notes-help">Six rows set the initial height above the minimum. Direction follows the text; labels follow the page.</p>
</div>import { Field, Textarea } from '@pelatform/react';
const notes = `Write a clear first paragraph.
Keep the original line breaks.
https://example.test/workspaces/international-customer-success`;
export function Example() {
return (
<Field label="Workspace notes" help="Six rows set the initial height above the minimum. Direction follows the text; labels follow the page.">
<Textarea rows={6} dir="auto" defaultValue={notes} />
</Field>
);
}import { Field, Textarea } from '@pelatform/react/prefixed';
const notes = `Write a clear first paragraph.
Keep the original line breaks.
https://example.test/workspaces/international-customer-success`;
export function Example() {
return (
<Field label="Workspace notes" help="Six rows set the initial height above the minimum. Direction follows the text; labels follow the page.">
<Textarea rows={6} dir="auto" defaultValue={notes} />
</Field>
);
}Auto resize
textarea-auto uses CSS field-sizing; browsers without it keep the rows height and the resize handle.
Five rows set the starting height; it follows the text up to a maximum, then the field scrolls.
<div class="field">
<label class="field-label" for="textarea-auto">Release notes</label>
<textarea class="textarea textarea-auto" id="textarea-auto" name="release-notes" rows="5" aria-describedby="textarea-auto-help" placeholder="Type several lines">The field grows with each new line.</textarea>
<p class="field-help" id="textarea-auto-help">Five rows set the starting height; it follows the text up to a maximum, then the field scrolls.</p>
</div><div class="plf:field">
<label class="plf:field-label" for="textarea-auto">Release notes</label>
<textarea class="plf:textarea plf:textarea-auto" id="textarea-auto" name="release-notes" rows="5" aria-describedby="textarea-auto-help" placeholder="Type several lines">The field grows with each new line.</textarea>
<p class="plf:field-help" id="textarea-auto-help">Five rows set the starting height; it follows the text up to a maximum, then the field scrolls.</p>
</div>import { Field, Textarea } from '@pelatform/react';
export function Example() {
return (
<Field label="Release notes" help="Five rows set the starting height; it follows the text up to a maximum, then the field scrolls.">
<Textarea name="release-notes" rows={5} autoResize placeholder="Type several lines" defaultValue="The field grows with each new line." />
</Field>
);
}import { Field, Textarea } from '@pelatform/react/prefixed';
export function Example() {
return (
<Field label="Release notes" help="Five rows set the starting height; it follows the text up to a maximum, then the field scrolls.">
<Textarea name="release-notes" rows={5} autoResize placeholder="Type several lines" defaultValue="The field grows with each new line." />
</Field>
);
}Grow after editing
Use data-plf-textarea-grow instead of textarea-auto when saved text should keep the rows height until someone edits it.
Saved text keeps the five-row height and scrolls; typing or deleting lets the field grow with the text.
<div class="field">
<label class="field-label" for="textarea-grow">Saved notes</label>
<textarea class="textarea" id="textarea-grow" name="saved-notes" rows="5" data-plf-textarea-grow aria-describedby="textarea-grow-help">Line 1 of the saved notes
Line 2
Line 3
Line 4
Line 5
Line 6
Line 7
Line 8
Line 9
Line 10</textarea>
<p class="field-help" id="textarea-grow-help">Saved text keeps the five-row height and scrolls; typing or deleting lets the field grow with the text.</p>
</div><div class="plf:field">
<label class="plf:field-label" for="textarea-grow">Saved notes</label>
<textarea class="plf:textarea" id="textarea-grow" name="saved-notes" rows="5" data-plf-textarea-grow aria-describedby="textarea-grow-help">Line 1 of the saved notes
Line 2
Line 3
Line 4
Line 5
Line 6
Line 7
Line 8
Line 9
Line 10</textarea>
<p class="plf:field-help" id="textarea-grow-help">Saved text keeps the five-row height and scrolls; typing or deleting lets the field grow with the text.</p>
</div>'use client';
import { useState } from 'react';
import { Field, Textarea } from '@pelatform/react';
const saved = `Line 1 of the saved notes
Line 2
Line 3
Line 4
Line 5
Line 6
Line 7
Line 8
Line 9
Line 10`;
export function Example() {
// The saved text keeps the five-row height until the first edit turns on autoResize.
const [edited, setEdited] = useState(false);
return (
<Field label="Saved notes" help="Saved text keeps the five-row height and scrolls; typing or deleting lets the field grow with the text.">
<Textarea name="saved-notes" rows={5} autoResize={edited} defaultValue={saved} onChange={() => setEdited(true)} />
</Field>
);
}'use client';
import { useState } from 'react';
import { Field, Textarea } from '@pelatform/react/prefixed';
const saved = `Line 1 of the saved notes
Line 2
Line 3
Line 4
Line 5
Line 6
Line 7
Line 8
Line 9
Line 10`;
export function Example() {
// The saved text keeps the five-row height until the first edit turns on autoResize.
const [edited, setEdited] = useState(false);
return (
<Field label="Saved notes" help="Saved text keeps the five-row height and scrolls; typing or deleting lets the field grow with the text.">
<Textarea name="saved-notes" rows={5} autoResize={edited} defaultValue={saved} onChange={() => setEdited(true)} />
</Field>
);
}JavaScript
Grow after editing needs JavaScript. The Full script (pelatform-ui.min.js) starts every component on page load, with no code of your own; with ESM imports, call init() yourself, as in Installation. For markup you add later, see Dynamic content.
<link rel="stylesheet" href="pelatform-ui.min.css">
<script src="pelatform-ui.min.js" defer></script><link rel="stylesheet" href="core/pelatform-ui-core.min.css">
<link rel="stylesheet" href="components/form.min.css">
<script src="core/pelatform-ui-core.min.js" defer></script>
<script src="components/textarea-grow.min.js" defer></script>
<script>document.addEventListener('DOMContentLoaded', () => PLF.init());</script><link rel="stylesheet" href="pelatform-ui.prefixed.min.css">
<script src="pelatform-ui.min.js" defer></script><link rel="stylesheet" href="core/pelatform-ui-core.prefixed.min.css">
<link rel="stylesheet" href="components/form.prefixed.min.css">
<script src="core/pelatform-ui-core.min.js" defer></script>
<script src="components/textarea-grow.min.js" defer></script>
<script>document.addEventListener('DOMContentLoaded', () => PLF.init());</script>| Attribute | Purpose |
|---|---|
data-plf-textarea-grow |
On a native textarea: keep the rows height until an edit, then fit the text |
| Method | Behavior |
|---|---|
PLF. |
Returns the instance, creating it for a textarea |
PLF. |
Returns the instance or null |
instance. |
Fits the current text now, for example after your code sets value |
instance. |
Removes the listeners and the set height |
A form reset restores the rows height; TextareaGrow fires no events, so listen to the native input event.
React
Textarea from @pelatform/react renders this native textarea, whose text is defaultValue, or value with onChange; inside a Field it takes the field’s label, help and error.
| Prop | Values | Default |
|---|---|---|
size |
sm, lg |
The default size |
autoResize |
true grows the field with its text (textarea-auto, through CSS field-sizing, so browsers without it keep the rows height); Grow after editing turns it on at the first edit |
false |
The other props are a native textarea’s, className and ref included.
CSS variables
Textarea uses the Input variables for its border, surface, text, radius, shadow and focus ring, plus these.
| Variable | Default |
|---|---|
--plf-input-height |
The minimum height is three times this value: 6. (108px); 6rem (96px) with textarea-sm, 7. (120px) with textarea-lg; when set, it takes precedence over the size classes |
--plf-line-height |
1. |
--plf-textarea-max-height |
20rem (320px), the largest height of textarea-auto and data-plf-textarea-grow |