PixelTextarea

Multi-line text input with label, hint, error chrome plus optional auto-grow and character counter.

  • Label / hint / error chrome via FieldShell — same DX as PixelInput
  • Optional autosize between minRows and maxRows, scrolling beyond the cap
  • Character counter (showCount) — total or N/max with overflow styling
  • Full tone + surface (pixel/linear) theming aligned with the rest of forms
  • Controlled — value + onChange (React), v-model (Vue), [(value)] or forms (Angular) — or uncontrolled with defaultValue
Status
stable
Category
forms
Since
v1.0.0

API

import { PixelTextarea } from '@pxlkit/ui-kit';
PropTypeDefaultDescription
labelstring—Floating label rendered above the textarea.
hintstring—Helper text shown below the field. Hidden when error is set.
errorstring—Error message shown below the field; flips visual state to invalid.
tone'green' | 'cyan' | 'gold' | 'red' | 'purple' | 'pink' | 'neutral''neutral'Visual tone for focus ring + border emphasis. Default: 'neutral'.
surface'pixel' | 'linear'—Surface variant. Inherits from PxlKitSurfaceProvider when omitted.
autosizeboolean—Auto-grow the textarea with content (between minRows and maxRows).
minRowsnumber3Minimum visible rows when autosize is on. Defaults to 3.
maxRowsnumber—Max rows before scrolling. Optional cap.
showCountboolean | { max?: number }—Render a character counter under the textarea. true shows N; { max } shows N/max.

Also takes the native attributes and event handlers of <textarea> (TextareaHTMLAttributes<HTMLTextAreaElement>).

ref points to <textarea>.

Accessibility

WCAG target: 2.1 AA

  • textbox

Wraps a native <textarea> so multiline textbox semantics are announced by assistive tech automatically. The label prop is wired through FieldShell, error toggles aria-invalid, and the hint/error it shows is exposed via aria-describedby, after any ids you pass. The counter uses aria-live="polite" so screen readers announce updates without stealing focus.

Keyboard

KeyActionWhen
TabMoves focus into and out of the textarea—
EnterInserts a newline within the textarea—
Shift+TabMoves focus to the previous focusable element—

Usage

import { PixelTextarea } from '@pxlkit/ui-kit';

export function Default() {
  return (
    <PixelTextarea
      label="Notes"
      placeholder="Write something..."
      hint="Up to 280 characters."
    />
  );
}

Examples

Default

import { PixelTextarea } from '@pxlkit/ui-kit';

export function Default() {
  return (
    <PixelTextarea
      label="Notes"
      placeholder="Write something..."
      hint="Up to 280 characters."
    />
  );
}

Uncontrolled

import { PixelTextarea } from '@pxlkit/ui-kit';

export function Uncontrolled() {
  return (
    <PixelTextarea
      label="Bio"
      defaultValue="Frontend engineer focused on retro UIs."
      hint="Edit freely."
    />
  );
}

Controlled

import { useState } from 'react';
import { PixelTextarea } from '@pxlkit/ui-kit';

export function Controlled() {
  const [value, setValue] = useState('');
  return (
    <PixelTextarea
      label="Message"
      value={value}
      onChange={(e) => setValue(e.target.value)}
      placeholder="Type to see live updates..."
      hint={`${value.length} chars`}
    />
  );
}

Tones

import { PixelTextarea } from '@pxlkit/ui-kit';

export function Tones() {
  return (
    <div className="flex flex-col gap-3">
      <PixelTextarea label="Neutral" tone="neutral" defaultValue="Neutral tone" />
      <PixelTextarea label="Green" tone="green" defaultValue="Green tone" />
      <PixelTextarea label="Cyan" tone="cyan" defaultValue="Cyan tone" />
      <PixelTextarea label="Gold" tone="gold" defaultValue="Gold tone" />
      <PixelTextarea label="Red" tone="red" defaultValue="Red tone" />
      <PixelTextarea label="Purple" tone="purple" defaultValue="Purple tone" />
      <PixelTextarea label="Pink" tone="pink" defaultValue="Pink tone" />
    </div>
  );
}

Surfaces

import { PixelTextarea } from '@pxlkit/ui-kit';

export function Surfaces() {
  return (
    <div className="flex flex-col gap-3">
      <PixelTextarea label="Pixel surface" surface="pixel" defaultValue="Chunky pixel chrome" />
      <PixelTextarea label="Linear surface" surface="linear" defaultValue="Sleek linear chrome" />
    </div>
  );
}

With error

import { PixelTextarea } from '@pxlkit/ui-kit';

export function WithError() {
  return (
    <PixelTextarea
      label="Feedback"
      defaultValue=""
      error="Feedback is required."
      placeholder="Tell us what went wrong..."
    />
  );
}

Disabled

import { PixelTextarea } from '@pxlkit/ui-kit';

export function Disabled() {
  return (
    <PixelTextarea
      label="Locked notes"
      disabled
      defaultValue="Cannot edit this field"
    />
  );
}

Autosize

import { useState } from 'react';
import { PixelTextarea } from '@pxlkit/ui-kit';

export function Autosize() {
  const [value, setValue] = useState(
    'Type more lines to watch this grow.\nIt will expand between minRows and maxRows.',
  );
  return (
    <PixelTextarea
      label="Auto-grow"
      autosize
      minRows={2}
      maxRows={8}
      value={value}
      onChange={(e) => setValue(e.target.value)}
    />
  );
}

With char count

import { useState } from 'react';
import { PixelTextarea } from '@pxlkit/ui-kit';

export function WithCharCount() {
  const [value, setValue] = useState('Short blurb');
  return (
    <PixelTextarea
      label="Short bio"
      value={value}
      onChange={(e) => setValue(e.target.value)}
      showCount={{ max: 140 }}
      placeholder="Up to 140 characters"
    />
  );
}

Required

import { PixelTextarea } from '@pxlkit/ui-kit';

export function Required() {
  return (
    <PixelTextarea
      label="Required"
      required
      placeholder="This field is required"
      hint="Don't leave it blank."
    />
  );
}

Related