---
title: Textarea
description: Edit multi-line text with optional auto-sizing and Field integration.
package: moraine
version: 0.6.0
repository: https://github.com/subframe7536/moraine
---

# Textarea

> Edit multi-line text with optional auto-sizing and Field integration.

Use Textarea for multi-line native text entry. Use [InputGroup](https://moraine.subf.dev/components/input-group.md) to put controls around it without changing which element receives typing and focus.

## Basic usage

```tsx
import { Field, Textarea } from 'moraine'

export function Example() {
  return (
    <Field label="Message">
      <Textarea placeholder="Write a message" />
    </Field>
  )
}
```

## Anatomy

```text
Textarea [component; slot=root; <textarea>]
```

## Usage

### Native text behavior

`Textarea` forwards native textarea attributes and works with controlled or uncontrolled values. Use `readOnly` to preserve selection and focus without edits, or `disabled` to prevent interaction.

```tsx
import { Textarea } from 'moraine'
import { createSignal } from 'solid-js'

export function NativeBehavior() {
  const [bio, setBio] = createSignal('Frontend developer passionate about accessible web UI.')

  return (
    <div class="max-w-md w-full space-y-3">
      <Textarea value={bio()} onValueChange={setBio} rows={3} />
      <p class="text-xs text-muted-foreground">
        Length: <span class="text-foreground font-mono">{bio().length}</span> characters
      </p>
    </div>
  )
}
```

### Size and composition

Enable autoresize and constrain it with `rows` and `maxRows`. Use `InputGroup orientation="vertical"` with `Leading` and `Trailing` for header and footer regions. Counters, submit buttons, and keyboard shortcuts remain application-owned behavior.

```tsx
import { Button, Textarea, InputGroup } from 'moraine'

export function Composition() {
  return (
    <div class="max-w-md w-full">
      <InputGroup orientation="vertical">
        <Textarea placeholder="Write a comment..." autoResize rows={2} maxRows={6} />
        <InputGroup.Trailing>
          <div class="flex w-full items-center justify-between">
            <span class="text-xs text-muted-foreground">Markdown supported</span>
            <Button size="xs">Comment</Button>
          </div>
        </InputGroup.Trailing>
      </InputGroup>
    </div>
  )
}
```

## Examples

### Autoresize

```tsx
import { Textarea } from 'moraine'
import { createSignal } from 'solid-js'

export function Autoresize() {
  const [post, setPost] = createSignal(
    'Just deployed the new Moraine component library docs! Smooth animations, fully typed SolidJS components, and accessible primitives out of the box 🚀',
  )
  const maxLength = 280

  return (
    <div class="p-4 b-1 b-border rounded-xl max-w-xl space-y-3">
      <label class="text-xs text-muted-foreground font-medium block">
        Draft Release Announcement
      </label>
      <Textarea
        autoResize
        rows={3}
        maxRows={8}
        value={post()}
        onValueChange={(next) => setPost(next ?? '')}
        placeholder="What's happening in your project?"
      />
      <div class="text-xs flex items-center justify-between">
        <span class="text-muted-foreground">Auto-expands as you type up to 8 rows.</span>
        <span
          class={`font-medium font-mono ${
            post().length > maxLength ? 'text-destructive' : 'text-muted-foreground'
          }`}
        >
          {post().length} / {maxLength}
        </span>
      </div>
    </div>
  )
}
```

### Header and footer

```tsx
import { Button, Icon, InputGroup, Textarea } from 'moraine'
import { createSignal } from 'solid-js'

export function HeaderFooter() {
  const [value, setValue] = createSignal('Hello Moraine!')

  return (
    <div class="gap-6 grid w-full lg:grid-cols-3">
      <InputGroup orientation="vertical">
        <InputGroup.Leading class="border-b border-border">
          <span class="font-semibold">Info text</span>
          <Icon name="i-lucide:info" class="text-base ms-auto" />
        </InputGroup.Leading>
        <Textarea placeholder="Ask, search or chat..." class="min-h-24" />
      </InputGroup>
      <InputGroup orientation="vertical">
        <Textarea
          value={value()}
          onValueChange={(next) => setValue(next ?? '')}
          placeholder="Write your message..."
          autoResize
          class="min-h-24"
        />
        <InputGroup.Trailing class="border-t border-border">
          <span>{value().length}/280 characters</span>
          <Button type="button" size="sm" class="ms-auto">
            Send
          </Button>
        </InputGroup.Trailing>
      </InputGroup>
      <InputGroup orientation="vertical">
        <InputGroup.Leading class="border-b border-border">
          <Icon name="i-lucide:code" class="text-base" />
          <span>script.js</span>
        </InputGroup.Leading>
        <Textarea placeholder="console.log('Hello, world!');" class="min-h-28" />
        <InputGroup.Trailing class="border-t border-border">
          <span>Line 1, Column 1</span>
          <span class="ms-auto">JavaScript</span>
        </InputGroup.Trailing>
      </InputGroup>
    </div>
  )
}
```

### Character count

```tsx
import { Textarea, InputGroup } from 'moraine'
import { createSignal } from 'solid-js'

const MAX_LENGTH = 160

export function CharacterCount() {
  const [value, setValue] = createSignal('')

  return (
    <InputGroup orientation="vertical">
      <Textarea
        value={value()}
        maxLength={MAX_LENGTH}
        onValueChange={setValue}
        placeholder="Add a short description..."
      />
      <InputGroup.Trailing>
        <span class="text-xs text-muted-foreground">
          {value().length}/{MAX_LENGTH}
        </span>
      </InputGroup.Trailing>
    </InputGroup>
  )
}
```

### Form integration

```tsx
import { Button, createForm, Textarea } from 'moraine'
import { createSignal } from 'solid-js'
import * as v from 'valibot'

export function FormIntegration() {
  const [submittedFeedback, setSubmittedFeedback] = createSignal('')
  const form = createForm({
    schema: v.object({
      feedback: v.pipe(
        v.string(),
        v.minLength(10, 'Feedback must be at least 10 characters long.'),
      ),
    }),
    initialInput: { feedback: '' },
    validate: 'input',
  })

  return (
    <form.Form onSubmit={(output) => setSubmittedFeedback(output.feedback)}>
      <div class="max-w-xl space-y-4">
        <form.Field
          name="feedback"
          label="Product Feedback"
          description="Tell us what features or fixes you'd like to see next."
          required
        >
          <Textarea placeholder="Write your detailed feedback here..." rows={4} autoResize />
        </form.Field>
        <div class="flex gap-3 items-center">
          <Button type="submit" variant="secondary" size="sm">
            Send Feedback
          </Button>
          <p class="text-xs text-muted-foreground">
            Characters submitted: {submittedFeedback().length}
          </p>
        </div>
      </div>
    </form.Form>
  )
}
```

## Attributes

| Attributes | Slot | Description |
| --- | --- | --- |
| `data-autoresize` | `textarea` | Present when the text control grows with its content. |
| `data-disabled` | `textarea` | Present when the component, slot, or item is disabled. |
| `data-invalid` | `textarea` | Present when the field or form has a validation error. |
| `data-readonly` | `textarea` | Present when the field is in read-only mode. |
| `data-required` | `textarea` | Present when the field input is required. |

## Props

Props for the Textarea component.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| autofocus | boolean \| undefined | false | Whether to automatically focus the textarea on mount. |
| autofocusDelay | number \| undefined | 0 | Delay in milliseconds before focusing the textarea. |
| autoResize | boolean \| undefined | false | Whether the textarea should automatically resize based on content. |
| autoResizeDelay | number \| undefined | 0 | Delay in milliseconds before triggering autoresize on mount. |
| defaultValue | string \| number \| undefined | — | The default value of the input (uncontrolled). |
| disabled | boolean \| undefined | false | Whether the input is disabled. |
| id | string \| undefined | — | The ID of the input element. |
| maxLength | number \| string \| undefined | — | Maximum character length for the textarea. |
| maxRows | number \| undefined | 0 | Maximum number of rows allowed during autoresize. |
| modelModifiers | M \| undefined | — | Modifiers for input processing (e.g., lazy, trim, number). |
| name | string \| undefined | — | The name of the input element, used for form submission. |
| onBlur | JSX.FocusEventHandlerUnion<HTMLTextAreaElement, FocusEvent> \| undefined | — | Native blur event handler. |
| onChange | JSX.EventHandlerUnion<HTMLTextAreaElement, Event> \| undefined | — | Native change event, after value synchronization and Field notification. |
| onFocus | JSX.FocusEventHandlerUnion<HTMLTextAreaElement, FocusEvent> \| undefined | — | Native focus event handler. |
| onInput | JSX.InputEventHandlerUnion<HTMLTextAreaElement, InputEvent> \| undefined | — | Native input event handler. |
| onValueChange | ((value: ModifierValue<M>) => void) \| undefined | — | Callback when the textarea value changes during input. |
| placeholder | string \| undefined | — | Placeholder text for the textarea. |
| readOnly | boolean \| undefined | false | Whether the input is read-only. |
| ref | Ref<HTMLTextAreaElement> \| undefined | — | Ref for the native textarea element. |
| required | boolean \| undefined | false | Whether the input is required. |
| rows | number \| undefined | 3 | Default number of rows. |
| size | 'sm' \| 'md' \| 'lg' \| undefined | 'md' | Visual size of the component. |
| value | string \| number \| undefined | — | The current value of the input (controlled). |
| variant | 'outline' \| 'subtle' \| 'ghost' \| 'none' \| undefined | 'outline' | Visual treatment of the component. |
| class | SlotClassValue \| undefined | — | Class applied to the component root or trigger element. |
| classes | Classes \| undefined | — | Family slot class defaults for this instance. |
| style | SlotStyleValue \| undefined | — | Style applied to the component root or trigger element. |
| styles | Styles \| undefined | — | Family slot style defaults for this instance. |
