Textarea
On this page
Textarea
Basic textarea with an external label and placeholder text.
Edit element properties
import '@rhds/elements/rh-textarea/rh-textarea.js';
label,
rh-textarea {
margin-inline: 20px;
max-inline-size: 320px;
}
label {
display: block;
font-family: var(--rh-font-family-body-text);
font-weight: var(--rh-font-weight-body-text-medium, 500);
margin-block: var(--rh-space-md, 8px);
}
<label for="my-textarea">
Notes
</label>
<rh-textarea id="my-textarea" placeholder="Enter your notes here"></rh-textarea>
import { Textarea } from "@rhds/elements/react/rh-textarea/rh-textarea.js";
// NOTE: React 19+ does not require these wrapper imports.
// You can use the custom elements directly as-is.
export const Demo = () => (
<label htmlFor="my-textarea">Notes</label>
<Textarea id="my-textarea" placeholder="Enter your notes here" />
);
Character Count
Textarea with a character counter showing current length vs maximum, using show-character-count and maxlength.
Edit element properties
import '@rhds/elements/rh-textarea/rh-textarea.js';
label,
rh-textarea {
margin-inline: 20px;
max-inline-size: 320px;
}
label {
display: block;
font-family: var(--rh-font-family-body-text);
font-weight: var(--rh-font-weight-body-text-medium, 500);
margin-block: var(--rh-space-md, 8px);
}
<label for="textarea-count">
Bio (max 200 characters)
</label>
<rh-textarea id="textarea-count" placeholder="Tell us about yourself" maxlength="200" show-character-count=""></rh-textarea>
import { Textarea } from "@rhds/elements/react/rh-textarea/rh-textarea.js";
// NOTE: React 19+ does not require these wrapper imports.
// You can use the custom elements directly as-is.
export const Demo = () => (
<label htmlFor="textarea-count">Bio (max 200 characters)</label>
<Textarea id="textarea-count" placeholder="Tell us about yourself" maxlength="200" show-character-count />
);
Color Context
Textarea rendered across light and dark color contexts to verify surface and text colors adapt correctly.
Edit element properties
import '@rhds/elements/lib/elements/rh-context-demo/rh-context-demo.js';
import '@rhds/elements/rh-textarea/rh-textarea.js';
label,
rh-textarea {
max-inline-size: 320px;
}
label {
display: block;
font-family: var(--rh-font-family-body-text);
font-weight: var(--rh-font-weight-body-text-medium, 500);
margin-block: var(--rh-space-md, 8px);
}
<rh-context-demo>
<label for="textarea-ctx">
Notes
</label>
<rh-textarea id="textarea-ctx" placeholder="Enter your notes here"></rh-textarea>
</rh-context-demo>
import { ContextDemo } from "@rhds/elements/react/rh-context-demo/rh-context-demo.js";
import { Textarea } from "@rhds/elements/react/rh-textarea/rh-textarea.js";
// NOTE: React 19+ does not require these wrapper imports.
// You can use the custom elements directly as-is.
export const Demo = () => (
<ContextDemo>
<label htmlFor="textarea-ctx">Notes</label>
<Textarea id="textarea-ctx" placeholder="Enter your notes here" />
</ContextDemo>
);
Disabled
Disabled textarea demonstrating aria-disabled pattern. Element remains focusable for assistive technology users.
Edit element properties
import '@rhds/elements/rh-textarea/rh-textarea.js';
label,
rh-textarea {
margin-inline: 20px;
max-inline-size: 320px;
}
label {
display: block;
font-family: var(--rh-font-family-body-text);
font-weight: var(--rh-font-weight-body-text-medium, 500);
margin-block: var(--rh-space-md, 8px);
}
rh-textarea {
margin-block-end: var(--rh-space-xl, 24px);
}
<label for="textarea-disabled">
Disabled textarea
</label>
<rh-textarea id="textarea-disabled" disabled="" placeholder="This textarea is disabled"></rh-textarea>
<label for="textarea-disabled-value">
Disabled with value
</label>
<rh-textarea id="textarea-disabled-value" disabled="" value="This text cannot be edited"></rh-textarea>
import { Textarea } from "@rhds/elements/react/rh-textarea/rh-textarea.js";
// NOTE: React 19+ does not require these wrapper imports.
// You can use the custom elements directly as-is.
export const Demo = () => (
<label htmlFor="textarea-disabled">Disabled textarea</label>
<Textarea id="textarea-disabled" disabled placeholder="This textarea is disabled" />
<label htmlFor="textarea-disabled-value">Disabled with value</label>
<Textarea id="textarea-disabled-value" disabled value="This text cannot be edited" />
);
Events
Demonstrates the input and change events fired by the textarea when a user interacts with it.
Edit element properties
import '@rhds/elements/rh-textarea/rh-textarea.js';
const textarea = document.querySelector('#textarea-events');
const form = document.querySelector('form');
const events = [];
form.addEventListener('submit', event => event.preventDefault());
const onTextareaEvent = (event) => {
events.push(event.type);
form.elements.events.value = events.join(', ');
};
textarea.addEventListener('input', onTextareaEvent);
textarea.addEventListener('change', onTextareaEvent);
label,
rh-textarea,
.container {
margin-inline: 20px;
margin-block-end: var(--rh-space-md, 8px);
}
label,
rh-textarea {
max-inline-size: 320px;
}
label {
display: block;
font-family: var(--rh-font-family-body-text);
font-weight: var(--rh-font-weight-body-text-medium, 500);
margin-block: var(--rh-space-md, 8px);
}
<form>
<label for="textarea-events">
Events
</label>
<rh-textarea id="textarea-events" placeholder="Type something here"></rh-textarea>
<p class="container">
An input event fires on each keystroke. A change event fires when the
textarea loses focus after its value has changed.
</p>
<fieldset class="container">
<legend>Events Fired</legend>
<output name="events">No events yet</output>
</fieldset>
</form>
import { Textarea } from "@rhds/elements/react/rh-textarea/rh-textarea.js";
// NOTE: React 19+ does not require these wrapper imports.
// You can use the custom elements directly as-is.
export const Demo = () => (
<form>
<label htmlFor="textarea-events">Events</label>
<Textarea id="textarea-events" placeholder="Type something here" />
<p className="container">An input event fires on each keystroke. A change event fires when the
textarea loses focus after its value has changed.</p>
<fieldset className="container">
<legend>Events Fired</legend>
<output name="events">No events yet</output>
</fieldset>
</form>
);
Field Sizing
Auto-growing textarea using the field-sizing attribute. Not supported in Firefox, where the textarea falls back to fixed height.
Edit element properties
import '@rhds/elements/rh-textarea/rh-textarea.js';
label,
rh-textarea {
margin-inline: 20px;
max-inline-size: 320px;
}
rh-textarea::part(textarea) {
min-block-size: 120px;
}
label {
display: block;
font-family: var(--rh-font-family-body-text);
font-weight: var(--rh-font-weight-body-text-medium, 500);
margin-block: var(--rh-space-md, 8px);
}
rh-textarea {
margin-block-end: var(--rh-space-xl, 24px);
}
<label for="textarea-grow">
Auto-growing textarea
</label>
<rh-textarea id="textarea-grow" field-sizing="content" placeholder="Type — the textarea grows with your content" rows="2"></rh-textarea>
import { Textarea } from "@rhds/elements/react/rh-textarea/rh-textarea.js";
// NOTE: React 19+ does not require these wrapper imports.
// You can use the custom elements directly as-is.
export const Demo = () => (
<label htmlFor="textarea-grow">Auto-growing textarea</label>
<Textarea id="textarea-grow" field-sizing="content" placeholder="Type — the textarea grows with your content" rows="2" />
);
Horizontal
Horizontal label layout with labels positioned to the left of the textarea using CSS Grid.
Edit element properties
import '@rhds/elements/rh-textarea/rh-textarea.js';
.form-horizontal {
display: grid;
grid-template-columns: max-content 1fr;
gap: var(--rh-space-md, 8px) var(--rh-space-lg, 16px);
align-items: start;
margin: 20px;
max-inline-size: 500px;
}
label {
font-family: var(--rh-font-family-body-text);
font-weight: var(--rh-font-weight-body-text-medium, 500);
padding-block-start: var(--rh-space-md, 8px);
}
input {
background-color: light-dark(
var(--rh-color-surface-lightest, #ffffff),
var(--rh-color-surface-darkest, #151515)
);
border: var(--rh-border-width-sm, 1px) solid var(--rh-color-border-subtle);
border-radius: var(--rh-border-radius-default, 3px);
color: var(--rh-color-text-primary);
font-family: var(--rh-font-family-body-text);
font-size: var(--rh-font-size-body-text-sm, 0.875rem);
line-height: var(--rh-line-height-body-text, 1.5);
padding: var(--rh-space-md, 8px) var(--rh-space-lg, 16px);
}
input:hover {
border-color: var(--rh-color-border-interactive);
}
input:focus {
border-color: transparent;
outline: var(--rh-border-width-md, 2px) solid var(--rh-color-border-interactive);
}
input:focus-visible {
border-color: var(--rh-color-border-interactive);
outline: 3px solid var(--rh-color-border-interactive);
outline-offset: 3px;
}
<form class="form-horizontal">
<label for="textarea-h1">First name</label>
<input type="text" id="textarea-h1" placeholder="First name">
<label for="textarea-h2">Last name</label>
<input type="text" id="textarea-h2" placeholder="Last name">
<label for="textarea-h3">Additional information</label>
<rh-textarea id="textarea-h3" placeholder="Notes"></rh-textarea>
</form>
import { Textarea } from "@rhds/elements/react/rh-textarea/rh-textarea.js";
// NOTE: React 19+ does not require these wrapper imports.
// You can use the custom elements directly as-is.
export const Demo = () => (
<form className="form-horizontal">
<label htmlFor="textarea-h1">First name</label>
<input type="text" id="textarea-h1" placeholder="First name" />
<label htmlFor="textarea-h2">Last name</label>
<input type="text" id="textarea-h2" placeholder="Last name" />
<label htmlFor="textarea-h3">Additional information</label>
<Textarea id="textarea-h3" placeholder="Notes" />
</form>
);
Localization
Localized validation messages using the property-based i18n pattern. Demonstrates Spanish and Hebrew (RTL) overrides.
Edit element properties
import '@rhds/elements/rh-textarea/rh-textarea.js';
section {
margin-block-end: var(--rh-space-2xl, 32px);
}
h3 {
font-family: var(--rh-font-family-heading);
margin-block-end: var(--rh-space-md, 8px);
}
label,
rh-textarea,
button {
margin-inline: 20px;
max-inline-size: 320px;
}
label {
display: block;
font-family: var(--rh-font-family-body-text);
font-weight: var(--rh-font-weight-body-text-medium, 500);
margin-block: var(--rh-space-md, 8px);
}
button {
margin-block-start: var(--rh-space-md, 8px);
}
.required {
color: var(--rh-color-status-danger);
}
<!-- Spanish example -->
<section lang="es">
<h3>Spanish</h3>
<form>
<label for="es-textarea">
Notas <span class="required" aria-label="Campo obligatorio">*</span>
</label>
<rh-textarea required="" minlength="10" id="es-textarea" name="notas" placeholder="Escriba sus notas aquí" value-missing-message="Por favor, complete este campo." too-short-message="Amplíe este texto a {minlength} caracteres o más (actualmente usa {length} caracteres)." too-long-message="Reduzca este texto a {maxlength} caracteres o menos (actualmente usa {length} caracteres).">
</rh-textarea>
<button type="submit">Enviar</button>
</form>
</section>
<!-- Hebrew (RTL) example -->
<section lang="he" dir="rtl">
<h3>Hebrew (RTL)</h3>
<form>
<label for="he-textarea">
הערות <span class="required" aria-label="שדה חובה">*</span>
</label>
<rh-textarea required="" minlength="10" id="he-textarea" name="notes" placeholder="הזינו את ההערות שלכם כאן" value-missing-message="נא למלא שדה זה." too-short-message="נא להאריך את הטקסט ל-{minlength} תווים לפחות (כרגע {length} תווים)." too-long-message="נא לקצר את הטקסט ל-{maxlength} תווים לכל היותר (כרגע {length} תווים).">
</rh-textarea>
<button type="submit">שלח</button>
</form>
</section>
import { Textarea } from "@rhds/elements/react/rh-textarea/rh-textarea.js";
// NOTE: React 19+ does not require these wrapper imports.
// You can use the custom elements directly as-is.
export const Demo = () => (
<section lang="es">
<h3>Spanish</h3>
<form>
<label htmlFor="es-textarea">
Notas
<span className="required" aria-label="Campo obligatorio">*</span>
</label>
<Textarea required minlength="10" id="es-textarea" name="notas" placeholder="Escriba sus notas aquí" value-missing-message="Por favor, complete este campo." too-short-message="Amplíe este texto a {minlength} caracteres o más (actualmente usa {length} caracteres)." too-long-message="Reduzca este texto a {maxlength} caracteres o menos (actualmente usa {length} caracteres)." />
<button type="submit">Enviar</button>
</form>
</section>
<section lang="he" dir="rtl">
<h3>Hebrew (RTL)</h3>
<form>
<label htmlFor="he-textarea">
הערות
<span className="required" aria-label="שדה חובה">*</span>
</label>
<Textarea required minlength="10" id="he-textarea" name="notes" placeholder="הזינו את ההערות שלכם כאן" value-missing-message="נא למלא שדה זה." too-short-message="נא להאריך את הטקסט ל-{minlength} תווים לפחות (כרגע {length} תווים)." too-long-message="נא לקצר את הטקסט ל-{maxlength} תווים לכל היותר (כרגע {length} תווים)." />
<button type="submit">שלח</button>
</form>
</section>
);
Readonly
Readonly textarea with pre-filled value. Content is not editable but the value is submitted with the form.
Edit element properties
import '@rhds/elements/rh-textarea/rh-textarea.js';
label,
rh-textarea,
button {
margin-inline: 20px;
max-inline-size: 320px;
}
label {
display: block;
font-family: var(--rh-font-family-body-text);
font-weight: var(--rh-font-weight-body-text-medium, 500);
margin-block: var(--rh-space-md, 8px);
}
button {
margin-block-start: var(--rh-space-md, 8px);
}
<form>
<label for="textarea-readonly">
Readonly notes
</label>
<rh-textarea id="textarea-readonly" readonly="" name="notes" value="This content is read-only. It cannot be edited by the user, but it is still submitted with the form."></rh-textarea>
<button type="submit">Submit</button>
</form>
import { Textarea } from "@rhds/elements/react/rh-textarea/rh-textarea.js";
// NOTE: React 19+ does not require these wrapper imports.
// You can use the custom elements directly as-is.
export const Demo = () => (
<form>
<label htmlFor="textarea-readonly">Readonly notes</label>
<Textarea id="textarea-readonly" readonly name="notes" value="This content is read-only. It cannot be edited by the user, but it is still submitted with the form." />
<button type="submit">Submit</button>
</form>
);
Required
Required textarea within a form that validates on submit and shows a browser validation message when empty.
Edit element properties
import '@rhds/elements/rh-textarea/rh-textarea.js';
label,
rh-textarea,
button {
margin-inline: 20px;
max-inline-size: 320px;
}
label {
display: block;
font-family: var(--rh-font-family-body-text);
font-weight: var(--rh-font-weight-body-text-medium, 500);
margin-block: var(--rh-space-md, 8px);
}
button {
margin-block-start: var(--rh-space-md, 8px);
}
.required {
color: var(--rh-color-status-danger);
}
<form>
<label for="my-textarea">
Notes <span class="required" aria-label="Required field">*</span>
</label>
<rh-textarea required="" id="my-textarea" name="notes" placeholder="Enter your notes here"></rh-textarea>
<button type="submit">Submit</button>
</form>
import { Textarea } from "@rhds/elements/react/rh-textarea/rh-textarea.js";
// NOTE: React 19+ does not require these wrapper imports.
// You can use the custom elements directly as-is.
export const Demo = () => (
<form>
<label htmlFor="my-textarea">
Notes
<span className="required" aria-label="Required field">*</span>
</label>
<Textarea required id="my-textarea" name="notes" placeholder="Enter your notes here" />
<button type="submit">Submit</button>
</form>
);
States
Textarea controls in success, warning, danger, and default states with help text displayed below each.
Edit element properties
import '@rhds/elements/rh-textarea/rh-textarea.js';
label,
rh-textarea {
margin-inline: 20px;
max-inline-size: 320px;
}
label {
display: block;
font-family: var(--rh-font-family-body-text);
font-weight: var(--rh-font-weight-body-text-medium, 500);
margin-block: var(--rh-space-md, 8px);
}
rh-textarea {
margin-block-end: var(--rh-space-xl, 24px);
}
<label for="textarea-success">
Success
</label>
<rh-textarea id="textarea-success" placeholder="Enter text" state="success" help-text="Success message"></rh-textarea>
<label for="textarea-warning">
Warning
</label>
<rh-textarea id="textarea-warning" placeholder="Enter text" state="warning" help-text="Warning message"></rh-textarea>
<label for="textarea-danger">
Danger
</label>
<rh-textarea id="textarea-danger" placeholder="Enter text" state="danger" help-text="Danger message"></rh-textarea>
<label for="textarea-default">
Default
</label>
<rh-textarea id="textarea-default" placeholder="Enter text" help-text="Help text"></rh-textarea>
<label for="textarea-slotted">
Slotted help text
</label>
<rh-textarea id="textarea-slotted" placeholder="Enter text">
<p slot="help-text">Slotted help text</p>
</rh-textarea>
import { Textarea } from "@rhds/elements/react/rh-textarea/rh-textarea.js";
// NOTE: React 19+ does not require these wrapper imports.
// You can use the custom elements directly as-is.
export const Demo = () => (
<label htmlFor="textarea-success">Success</label>
<Textarea id="textarea-success" placeholder="Enter text" state="success" help-text="Success message" />
<label htmlFor="textarea-warning">Warning</label>
<Textarea id="textarea-warning" placeholder="Enter text" state="warning" help-text="Warning message" />
<label htmlFor="textarea-danger">Danger</label>
<Textarea id="textarea-danger" placeholder="Enter text" state="danger" help-text="Danger message" />
<label htmlFor="textarea-default">Default</label>
<Textarea id="textarea-default" placeholder="Enter text" help-text="Help text" />
<label htmlFor="textarea-slotted">Slotted help text</label>
<Textarea id="textarea-slotted" placeholder="Enter text">
<p slot="help-text">Slotted help text</p>
</Textarea>
);
Other libraries
To learn more about our other libraries, visit this page.
Feedback
To give feedback about anything on this page, contact us.