Skip to main content Home About the Design SystemRoadmap OverviewDesignersDevelopers OverviewColorGridIconographyInteractionsSpacingTypography Overview Global colorBox shadowTypographyBorderOpacitySpaceLengthIconBreakpointsMedia queries All elements Accordion Alert Announcement Audio player Avatar Back to top Badge Blockquote Breadcrumb Button group Button Card Chip Code block Call to action Dialog Disclosure Footer Health index Icon Jump links Menu dropdown Navigation link Navigation (primary) Navigation (secondary) Navigation (vertical) Pagination PopoverPlanned Progress stepper ReadtimeNew Scheme toggle SelectNew Site status Skeleton Skip link Spinner Statistic Subnavigation Surface Switch Table Tabs Tag Textarea Tile Timestamp Tooltip Video embed OverviewColor PalettesCustomizingDevelopers All PatternsAccordionAlertCall to ActionCardFilterFormLink with iconLogo wallSearch barSticky bannerSticky cardTabsTagTile All Personalization PatternsAnnouncement FundamentalsAccessibility toolsAssistive technologiesCI/CDContentContributorsDesignDevelopmentManual testingResourcesScreen readers Design/code status Release notes Get support

Textarea

OverviewStyleGuidelinesCodeAccessibilityDemos
TextareaCharacter CountColor ContextDisabledEventsField SizingHorizontalLocalizationReadonlyRequiredStatesTextareaCharacter CountColor ContextDisabledEventsField SizingHorizontalLocalizationReadonlyRequiredStates

Textarea

Basic textarea with an external label and placeholder text.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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>
);
© 2026 Red Hat Deploys by Netlify