Getting StartedInstall and bootstrap headless foundations 4
LayoutHeadless structural primitives for expandable and container patterns 6
OverlayHeadless modal and floating layer behavior 3
FeedbackHeadless notification and status communication patterns 5
FormHeadless input and selection contracts 17
UtilityReusable action, identity, and clipboard behavior 6
NavigationHeadless trails, trees, menus, and command surfaces 7

Textarea

tngTextarea is the multiline primitive for native <textarea> elements. It layers rows and resize behavior on top of the same headless input contract used by tngInput.

Use direct primitive attachment for a simple multiline field, or add tngInputGroup when the textarea needs shared shell chrome and trailing helper content.

What you get

  • Real native <textarea> semantics for editing, selection, and IME input.
  • rows normalization and a stable data-resize hook for styling.
  • The same grouped composition model as headless input via tngInputGroup.
  • State hooks and ARIA pass-through without taking ownership of your visual design.

Simple examples

Compare the same headless textarea structure across plain CSS and Tailwind CSS styles.

Headless textarea (Plain CSS)

Installation

Import the multiline primitive and optional group primitives only when you need them.

Primitives import

ts
import { TngTextarea, TngInputGroup, TngSuffix } from '@tailng-ui/primitives';

Basic usage

1) Direct primitive attachment

Direct tngTextarea usage

html
<textarea tngTextarea rows="4" placeholder="Write release notes"></textarea>

2) Grouped textarea with shared shell

Add tngInputGroup when the textarea needs shared chrome, a helper suffix, or future adornments that should live inside the same container.

Grouped textarea composition

html
<div tngInputGroup class="release-notes-shell">
  <textarea tngTextarea rows="5" placeholder="Summarize the rollout"></textarea>
  <span tngSuffix aria-hidden="true">Autosaves</span>
</div>

Structure

The primitive never replaces the native element. You apply tngTextarea to a real <textarea>, and optionally wrap it with tngInputGroup to get leading/control/trailing regions and mirrored focus/invalid state hooks.

This lets multiline controls participate in the same slot-driven styling contract as other headless form primitives without coupling the DOM to a specific CSS framework.

Rows and resize

  • rows is normalized to at least 1.
  • resize supports vertical, horizontal, both, and none.
  • The primitive emits data-resize so design tokens can branch on the chosen resize mode.

Accessibility guidance

  • Use a real label or ARIA name just as you would for any other native textarea.
  • Decorative trailing status text should stay aria-hidden="true".
  • Prefer helper text outside the group shell for longer validation or instructional copy.

Validation patterns

Recommended for unit tests (jsdom)

Stable invalid test textarea

html
<textarea tngTextarea rows="4" aria-invalid="true"></textarea>

Native browser validation path

Native required validation

html
<textarea tngTextarea rows="4" required></textarea>

Examples

Release notes composer

Release notes + autosave meta

html
<div tngInputGroup class="release-notes-shell">
  <textarea
    tngTextarea
    rows="5"
    aria-label="Release notes"
    [value]="releaseNotes()"
    (input)="onReleaseNotesInput($event)"
  ></textarea>
  <span tngSuffix aria-hidden="true">Autosaves</span>
</div>

Readonly review state

Readonly textarea

html
<textarea
  tngTextarea
  rows="4"
  readonly
  aria-label="Readonly incident summary"
>Incident summary is locked while the review is pending.</textarea>

Common pitfalls

Correct

Textarea registered with the primitive

html
<div tngInputGroup>
  <textarea tngTextarea rows="4"></textarea>
</div>

Incorrect

Missing tngTextarea

html
<div tngInputGroup>
  <textarea rows="4"></textarea>
</div>