Text Field

Textarea

Multi-line text input for longer content like messages, descriptions, or comments.

View as Markdown
stable
textarea multiline text message

Interactive demo of textarea field

Share your thoughts with us

Maximum 200 characters

/**
 * Textarea Field Demo - Interactive examples of textarea fields
 */

import { Form, FormBuilder } from '@saastro/forms';

import { FormProvider } from '@/components/FormProvider';
import { TooltipProvider } from '@/components/ui/tooltip';

const config = FormBuilder.create('textarea-demo')
  .layout('manual')
  .columns(12)
  .addField('message', (f) =>
    f
      .type('textarea')
      .label('Message')
      .placeholder('Write your message here...')
      .helperText('Share your thoughts with us')
      .rows(4)
      .required('Message is required')
      .minLength(10, 'Message must be at least 10 characters')
      .columns({ default: 12 }),
  )
  .addField('bio', (f) =>
    f
      .type('textarea')
      .label('Bio')
      .placeholder('Tell us about yourself...')
      .helperText('Maximum 200 characters')
      .rows(3)
      .maxLength(200)
      .optional()
      .columns({ default: 12 }),
  )
  .addStep('main', ['message', 'bio'])
  .build();

export default function TextareaDemo() {
  const handleSubmit = (data: Record<string, unknown>) => {
    console.log('Form submitted:', data);
    alert('Form submitted! Check console for data.');
  };

  return (
    <TooltipProvider>
      <FormProvider>
        <Form config={config} onSubmit={handleSubmit} className="space-y-4" />
      </FormProvider>
    </TooltipProvider>
  );
}

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

import { FormBuilder } from '@saastro/forms';

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

.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 below).

With Rows Configuration

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

JSON Configuration

{
  "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

PropTypeDefaultDescription
type'textarea'-Field type (required)
labelstring-Label text
placeholderstring-Placeholder text
helperTextstring-Help text below field
rowsnumber-Number of visible rows (native rows attribute; browser default applies if omitted)
maxLengthnumber-Maximum character count (native HTML attribute, enforced by the browser)
schemaZodType | ValidationRules-Validation — required by the type; use serializable rules in JSON configs
size'xs' | 'sm' | 'md' | 'lg' | 'xl''md'Input size variant
columnsPartial<Record<Breakpoint, number>>-Grid columns by breakpoint
disabledboolean | function | ConditionGroupfalseDisable the field
hiddenboolean | function | ConditionGroup | ResponsivefalseHide the field

Validation

Required with Minimum Length

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

Maximum Length Validation

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

With Zod Schema

import { z } from 'zod';

.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

.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

.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.

JSON Styling

{
  "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 }
}
  • Text - Single-line text input
  • 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