# Textarea

> Native textarea with terminal styling, an opt-in monospace variant, and an auto-resize mode that grows the control with its content.

- Category: form
- Status: stable (since 0.3.0)
- A11y pattern: https://www.w3.org/WAI/ARIA/apg/patterns/
- Tokens: --background-quaternary, --foreground-primary, --foreground-secondary, --font-sans, --font-mono, --danger-color
- Playground: https://design.freecodecamp.org/playground#textarea
- npm dependencies: `react@>=18 <20`
- Registry dependencies: [theme](https://design.freecodecamp.org/registry/theme.md)
- Files:
  - `Textarea.tsx` → `src/ui/textarea/Textarea.tsx` (raw: https://design.freecodecamp.org/registry/textarea/Textarea.tsx)
  - `textarea.css` → `src/ui/textarea/textarea.css` (raw: https://design.freecodecamp.org/registry/textarea/textarea.css)

## Install (copy source)

1. Ensure the theme is installed once per project - tokens.css + base.css imported globally, fonts available. See https://design.freecodecamp.org/registry/theme.md and https://design.freecodecamp.org/registry/starter.md.
2. Copy the files below into `src/ui/textarea/` (adjust to your project layout) and import the CSS once from your global stylesheet, e.g. `@import './ui/textarea/textarea.css';`.
3. Colors, spacing and type come from tokens - tailor the component by editing the copied source; recolour by editing tokens.css, not the component CSS.

## Usage

Textarea is the native multi-line input, restyled to match the rest of
the terminal form shell. Drop in `variant="mono"` for code and command
capture; flip `autoResize` on when the control should grow and shrink
with its content instead of using the default vertical scroll affordance.

## Accessibility

Native `<textarea>` - every screen reader and keyboard affordance works
out of the box. Pair with a `<label>` (or FormGroup + HelpBlock) for
the accessible name. When `invalid` is set, surface a matching error
message through HelpBlock so the assistive tech announcement has
something to read.

## Example

```tsx
import { Textarea } from './ui/textarea/Textarea';

<Textarea
  id="bio"
  rows={3}
  placeholder="What are you learning right now?"
/>
```

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `variant` | `enum` | no | `default` |  |
| `invalid` | `boolean` | no | - |  |
| `autoResize` | `boolean` | no | - |  |

## Source: Textarea.tsx

```tsx
import React, {
  forwardRef,
  useEffect,
  useImperativeHandle,
  useRef
} from 'react';

export type TextareaVariant = 'default' | 'mono';

export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
  variant?: TextareaVariant;
  invalid?: boolean;
  autoResize?: boolean;
}

function fit(el: HTMLTextAreaElement | null): void {
  if (!el) return;
  el.style.height = 'auto';
  el.style.height = `${el.scrollHeight}px`;
}

export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
  (
    {
      className = '',
      variant = 'default',
      invalid,
      autoResize,
      onInput,
      ...rest
    },
    ref
  ) => {
    const innerRef = useRef<HTMLTextAreaElement | null>(null);
    useImperativeHandle(ref, () => innerRef.current as HTMLTextAreaElement);
    useEffect(() => {
      if (!autoResize) return;
      fit(innerRef.current);
    }, [autoResize, rest.value, rest.defaultValue]);
    const classes = [
      'textarea',
      variant !== 'default' && `textarea--${variant}`,
      className
    ]
      .filter(Boolean)
      .join(' ');
    return (
      <textarea
        ref={innerRef}
        className={classes}
        aria-invalid={invalid || undefined}
        data-auto-resize={autoResize ? 'true' : undefined}
        onInput={e => {
          if (autoResize) fit(e.currentTarget);
          onInput?.(e);
        }}
        {...rest}
      />
    );
  }
);
Textarea.displayName = 'Textarea';
```

## Source: textarea.css

```css
.textarea {
  display: block;
  width: 100%;
  min-height: 80px;
  font-family: var(--font-sans);
  font-size: var(--fs-md);
  line-height: var(--lh-base);
  color: var(--foreground-primary);
  background-color: var(--background-quaternary);
  border: var(--border-width-thin) solid var(--foreground-secondary);
  padding: 8px 10px;
  resize: vertical;
  transition:
    border-color 120ms,
    background-color 120ms;
}
.textarea:focus-visible {
  outline: none;
  border-color: var(--foreground-primary);
}
.textarea[aria-invalid='true'] {
  border-color: var(--danger-color);
}
.textarea:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.textarea--mono {
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  letter-spacing: 0.01em;
}
.textarea[data-auto-resize='true'] {
  resize: none;
  overflow: hidden;
}
```

## HTML / vanilla variant

```html
<textarea class="input input--textarea" id="bio" rows="3"></textarea>
```

Interactive behaviours for plain HTML come from the vanilla runtime (data-uikit-* attributes): https://design.freecodecamp.org/registry/vanilla.md - or download https://design.freecodecamp.org/cdn/uikit.global.js once and self-host it (do not hotlink).

## For coding agents

This library is distributed as copyable source, not an npm package. Start at https://design.freecodecamp.org/registry/starter.md, discover components via https://design.freecodecamp.org/llms.txt, and copy files into the consuming project. Keep token names intact; recolour by editing the copied tokens.css.
