# Field: Textarea (category: text)

URL: https://docs.forms.saastro.io/docs/textarea

<FieldPreview name="textarea" description="Interactive demo of textarea field" />

## Overview

The textarea field provides a multi-line text input for longer content. It supports configurable rows and a native `maxLength` character cap.

## Usage

### Basic Textarea

```tsx

const config = FormBuilder.create('feedback')
  .addField('message', (f) =>
    f
      .type('textarea')
      .label('Your Message')
      .placeholder('Write your message here...')
      .required('Message is required')
      .minLength(10, 'Message must be at least 10 characters'),
  )
  .addStep('main', ['message'])
  .build();
```

Every non-hidden field built with `FieldBuilder` must declare validation — chain `.required()`, `.minLength()`, a Zod `.schema()`, or `.optional()` (no rules) before `build()`.

### With Character Limit

```tsx
.addField('bio', (f) =>
  f.type('textarea')
    .label('Bio')
    .placeholder('Tell us about yourself...')
    .maxLength(500)
    .helperText('Maximum 500 characters')
    .rows(4)
    .optional()
)
```

`maxLength` sets the native HTML attribute, so the browser enforces the cap — no character counter is displayed. To also show a validation message, pair it with `.maxLengthValidation()` (see [Validation](#validation) below).

### With Rows Configuration

```tsx
.addField('description', (f) =>
  f.type('textarea')
    .label('Description')
    .rows(6)
    .placeholder('Detailed description...')
    .optional()
)
```

### JSON Configuration

```json
{
  "type": "textarea",
  "label": "Message",
  "placeholder": "Write your message...",
  "rows": 4,
  "maxLength": 500,
  "schema": {
    "required": true,
    "minLength": 10,
    "minLengthMessage": "Message must be at least 10 characters"
  }
}
```

## Props

| Prop          | Type                                                  | Default | Description                              |
| ------------- | ----------------------------------------------------- | ------- | ---------------------------------------- |
| `type`        | `'textarea'`                                          | -       | Field type (required)                    |
| `label`       | `string`                                              | -       | Label text                               |
| `placeholder` | `string`                                              | -       | Placeholder text                         |
| `helperText`  | `string`                                              | -       | Help text below field                    |
| `rows`        | `number`                                              | -       | Number of visible rows (native `rows` attribute; browser default applies if omitted) |
| `maxLength`   | `number`                                              | -       | Maximum character count (native HTML attribute, enforced by the browser) |
| `schema`      | `ZodType \| ValidationRules`                          | -       | Validation — required by the type; use serializable rules in JSON configs |
| `size`        | `'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl'`                | `'md'`  | Input size variant                       |
| `columns`     | `Partial<Record<Breakpoint, number>>`                 | -       | Grid columns by breakpoint               |
| `disabled`    | `boolean \| function \| ConditionGroup`               | `false` | Disable the field                        |
| `hidden`      | `boolean \| function \| ConditionGroup \| Responsive` | `false` | Hide the field                           |

## Validation

### Required with Minimum Length

```tsx
.addField('feedback', (f) =>
  f.type('textarea')
    .label('Feedback')
    .required('Feedback is required')
    .minLength(20, 'Please provide at least 20 characters')
)
```

### Maximum Length Validation

```tsx
.addField('tweet', (f) =>
  f.type('textarea')
    .label('Tweet')
    .maxLength(280)
    .maxLengthValidation(280, 'Tweet cannot exceed 280 characters')
)
```

### With Zod Schema

```tsx

.addField('essay', (f) =>
  f.type('textarea')
    .label('Essay')
    .schema(z.string()
      .min(100, 'Essay must be at least 100 characters')
      .max(5000, 'Essay cannot exceed 5000 characters')
    )
)
```

## Styling

### Custom Classes

```tsx
.addField('message', (f) =>
  f.type('textarea')
    .label('Message')
    .rows(4)
    .required()
    .classNames({
      wrapper: 'bg-muted/30 p-4 rounded-lg',
      input: 'min-h-[120px] resize-none',
      label: 'text-lg font-medium',
      error: 'text-destructive text-sm',
      helper: 'text-muted-foreground text-xs',
    })
)
```

### Responsive Layout

```tsx
.addField('description', (f) =>
  f.type('textarea')
    .label('Description')
    .rows(5)
    .columns({ default: 12, md: 8 })
    .size('lg')
    .optional()
)
```

Per-field `columns` only take effect when the form uses manual layout (`.layout('manual')`); in auto layout they are ignored. See the [Layout guide](/docs/layout).

### JSON Styling

```json
{
  "type": "textarea",
  "label": "Message",
  "rows": 4,
  "wrapper_className": "bg-muted/30 p-4 rounded-lg",
  "input_className": "min-h-[120px] resize-none",
  "label_className": "text-lg font-medium",
  "columns": { "default": 12 },
  "size": "lg",
  "schema": { "required": false }
}
```

## Related Fields

- [Text](/docs/text) - Single-line text input
- [Input Group](/docs/input-group) - Text input with prefix/suffix

## Accessibility

- Native `<textarea>` element for full accessibility
- Proper labeling via `label` prop
- Supports keyboard navigation
- `maxLength` is enforced natively by the browser
