bpdm/ui
Form

Textarea

Multi-line text input — sizes, resize control, auto-grow, and a character counter, in React (@bpdm/ui) and Angular (@bpdm/ng).

The Textarea is a multi-line text field on the same tokens as Input — three sizes, a resize control, optional autoResize (grows with content), and a showCount character counter. In React it's <Textarea>; in Angular it's the bpdmTextarea directive on a native <textarea>.

Usage

Drop it in like a native <textarea> — it's full-width, themed, and accessible by default.

comment.tsx
import { Textarea } from '@bpdm/ui/textarea';

export function Comment() {
  return <Textarea placeholder="Write a comment…" />;
}
comment.ts
import { Component } from '@angular/core';
import { BpdmTextarea } from '@bpdm/ng';

@Component({
  selector: 'comment',
  imports: [BpdmTextarea],
  template: `<textarea bpdmTextarea placeholder="Write a comment…"></textarea>`,
})
export class Comment {}

Sizes

size sets the padding and text scale — sm, md (default), or lg.

sizes.tsx
import { Textarea } from '@bpdm/ui/textarea';

export function Sizes() {
  return (
    <div className="flex flex-col gap-3">
      <Textarea size="sm" placeholder="Small" />
      <Textarea size="md" placeholder="Medium (default)" />
      <Textarea size="lg" placeholder="Large" />
    </div>
  );
}
sizes.ts
import { Component } from '@angular/core';
import { BpdmTextarea } from '@bpdm/ng';

@Component({
  selector: 'sizes',
  imports: [BpdmTextarea],
  template: `
    <div class="flex flex-col gap-3">
      <textarea bpdmTextarea size="sm" placeholder="Small"></textarea>
      <textarea bpdmTextarea size="md" placeholder="Medium (default)"></textarea>
      <textarea bpdmTextarea size="lg" placeholder="Large"></textarea>
    </div>
  `,
})
export class Sizes {}

Resize

resize controls the drag handle — vertical (default), none, or both.

resize.tsx
import { Textarea } from '@bpdm/ui/textarea';

export function Resize() {
  return (
    <div className="flex flex-col gap-3">
      <Textarea resize="none" placeholder="This one can't be resized." />
      <Textarea resize="both" placeholder="Drag the corner to resize." />
    </div>
  );
}
resize.ts
import { Component } from '@angular/core';
import { BpdmTextarea } from '@bpdm/ng';

@Component({
  selector: 'resize',
  imports: [BpdmTextarea],
  template: `
    <div class="flex flex-col gap-3">
      <textarea bpdmTextarea resize="none" placeholder="This one can't be resized."></textarea>
      <textarea bpdmTextarea resize="both" placeholder="Drag the corner to resize."></textarea>
    </div>
  `,
})
export class Resize {}

Auto-resize

Set autoResize and the field grows to fit its content — no scrollbar, and manual resize is turned off.

auto-resize.tsx
import { Textarea } from '@bpdm/ui/textarea';

export function AutoResize() {
  return <Textarea autoResize placeholder="Type — I grow to fit." />;
}
auto-resize.ts
import { Component } from '@angular/core';
import { BpdmTextarea } from '@bpdm/ng';

@Component({
  selector: 'auto-resize',
  imports: [BpdmTextarea],
  template: `<textarea bpdmTextarea autoResize placeholder="Type — I grow to fit."></textarea>`,
})
export class AutoResize {}

Character count

Pass showCount to show a live counter, paired with maxLength (React) / maxlength (Angular) for an n / max readout.

43 / 120
count.tsx
import { Textarea } from '@bpdm/ui/textarea';

export function Count() {
  return <Textarea showCount maxLength={120} placeholder="Up to 120 characters…" />;
}
count.ts
import { Component } from '@angular/core';
import { BpdmTextarea } from '@bpdm/ng';

@Component({
  selector: 'count',
  imports: [BpdmTextarea],
  template: `
    <textarea bpdmTextarea showCount maxlength="120" placeholder="Up to 120 characters…"></textarea>
  `,
})
export class Count {}

States

Disabled dims and blocks input; aria-invalid turns the border destructive-red.

states.tsx
import { Textarea } from '@bpdm/ui/textarea';

export function States() {
  return (
    <div className="flex flex-col gap-3">
      <Textarea disabled defaultValue="You can't edit this field." />
      <Textarea aria-invalid defaultValue="This value needs attention." />
    </div>
  );
}
states.ts
import { Component } from '@angular/core';
import { BpdmTextarea } from '@bpdm/ng';

@Component({
  selector: 'states',
  imports: [BpdmTextarea],
  template: `
    <div class="flex flex-col gap-3">
      <textarea bpdmTextarea disabled value="You can't edit this field."></textarea>
      <textarea bpdmTextarea aria-invalid="true" value="This value needs attention."></textarea>
    </div>
  `,
})
export class States {}

API

Textarea / textarea[bpdmTextarea]

Extends the native <textarea>placeholder, value, defaultValue, rows, maxLength, disabled, onChange, etc. all work as usual.

PropTypeDefaultDescription
sizesm | md | lgmdPadding + text scale.
resizenone | vertical | bothverticalDrag-handle behaviour.
autoResizebooleanfalseGrow to fit content (disables manual resize).
showCountbooleanfalseShow a character counter (pairs with maxLength / maxlength), linked to the field via aria-describedby.
classNamestring (Angular class)Extra classes.

Accessibility

  • Renders a native <textarea>, so it's focusable and labelable out of the box — associate a <label> (via htmlFor/id) or aria-label.
  • Invalid state is set with aria-invalid — it colours the border and is announced by assistive tech (never colour-only).
  • Disabled uses the native disabled attribute (removed from the tab order).
  • showCount renders a character counter that's automatically linked to the field via aria-describedby, so screen readers get the current count and limit as the field's description (any aria-describedby you pass is preserved).

Internationalization

  • The textarea renders no text of its own — pass a translated placeholder / accessible name; the showCount readout is numeric (n / max), so it's locale-neutral.
  • RTL-safe: alignment uses a logical property (text-end), so it mirrors under dir="rtl". See the Internationalization guide.

On this page