# Textarea (/ui/components/react/textarea)



<Playground component="textarea" />

## Usage [#usage]

```tsx
import { Textarea } from '@appica/ui-react/textarea'
```

```tsx
<Textarea rows={4} placeholder="Write a message…" />
```

`Textarea` is the multi-line counterpart to [`Input`](/ui/components/react/input), sharing its appearance, variants, and sizes. Use it like a native `<textarea>` — native attributes pass straight through — and give it a label with an external `<label htmlFor>` or an `aria-label`.

`rows` seeds the initial height (and the resize floor); the field can still be dragged taller by the resize handle. Add adornments with `startSlot` / `endSlot` and a clear button with `clearable`.

## Examples [#examples]

### Variants [#variants]

`variant` switches the appearance between a bordered `outline` (default) and a filled `soft` field.

```tsx
import { Textarea } from '@appica/ui-react/textarea'

export default function TextareaVariants() {
  return (
    <div className="flex w-full max-w-70 flex-col gap-4">
      <Textarea variant="outline" placeholder="Outline" aria-label="Outline" />
      <Textarea variant="soft" placeholder="Soft" aria-label="Soft" />
    </div>
  )
}
```

### Sizes [#sizes]

`inputSize` scales the padding and text — `sm`, `md` (default), or `lg`. (It's named `inputSize` so it doesn't collide with any native attribute.)

```tsx
import { Textarea } from '@appica/ui-react/textarea'

export default function TextareaSizes() {
  return (
    <div className="flex w-full max-w-70 flex-col gap-4">
      <Textarea inputSize="sm" placeholder="Small" aria-label="Small" />
      <Textarea inputSize="md" placeholder="Medium" aria-label="Medium" />
      <Textarea inputSize="lg" placeholder="Large" aria-label="Large" />
    </div>
  )
}
```

### Start & end slots [#start--end-slots]

`startSlot` and `endSlot` take any node — not just icons — aligned to the first line of the field. Use them to build a compact composer: an [`Avatar`](/ui/components/react/avatar) with a send [`Button`](/ui/components/react/button), or a live character counter.

```tsx
'use client'

import { useState } from 'react'
import { Textarea } from '@appica/ui-react/textarea'
import { Avatar, AvatarFallback } from '@appica/ui-react/avatar'
import { Button } from '@appica/ui-react/button'
import { Send2, UserFilled } from '@appica/icons-react'

const LIMIT = 180

export default function TextareaSlots() {
  const [value, setValue] = useState('')
  return (
    <div className="flex w-full max-w-70 flex-col gap-4">
      <Textarea
        className="*:data-[slot=textarea-end]:h-full *:data-[slot=textarea-end]:items-end"
        rows={4}
        startSlot={
          <Avatar size="2xs">
            <AvatarFallback>
              <UserFilled />
            </AvatarFallback>
          </Avatar>
        }
        endSlot={
          <Button variant="soft" size="icon-sm" aria-label="Send comment">
            <Send2 />
          </Button>
        }
        placeholder="Add a comment…"
        aria-label="Comment"
      />
      <Textarea
        rows={3}
        value={value}
        onChange={(event) => setValue(event.target.value.slice(0, LIMIT))}
        endSlot={
          <span className="text-foreground-muted text-xs tabular-nums">
            {value.length}/{LIMIT}
          </span>
        }
        placeholder="What's on your mind?"
        aria-label="Status"
      />
    </div>
  )
}
```

### Clearable [#clearable]

Set `clearable` to add a clear (✕) button that appears once the field has a value and empties it on click. Pair it with `onClear` to react to the reset.

```tsx
import { Textarea } from '@appica/ui-react/textarea'

export default function TextareaClearable() {
  return (
    <Textarea
      className="max-w-70"
      clearable
      defaultValue="The quick brown fox jumps over the lazy dog."
      aria-label="Notes"
    />
  )
}
```

### Disabled, read-only & error states [#disabled-read-only--error-states]

`disabled` greys out the field and blocks interaction; `readOnly` keeps it focusable and selectable but prevents edits. Set `aria-invalid` to paint the error state — it's mirrored to `data-invalid`.

```tsx
import { Textarea } from '@appica/ui-react/textarea'

export default function TextareaStates() {
  return (
    <div className="flex w-full max-w-70 flex-col gap-4">
      <Textarea placeholder="Disabled" disabled aria-label="Disabled" />
      <Textarea defaultValue="Read-only content" readOnly aria-label="Read-only" />
      <Textarea defaultValue="Too short" aria-invalid aria-label="Notes with error" />
    </div>
  )
}
```

## RTL [#rtl]

Every Appica UI component supports right-to-left layouts out of the box. Set the `dir` attribute on a container (commonly your `<html>` element) so CSS logical properties resolve correctly, and wrap your tree in `DirectionProvider` so direction-aware behavior — roving focus, popup placement, and the like — follows the same direction.

```tsx
import { DirectionProvider } from '@appica/ui-react/providers/direction-provider'

export default function RootLayout({ children }) {
  return (
    <html lang="ar" dir="rtl">
      <body>
        <DirectionProvider dir="rtl">{children}</DirectionProvider>
      </body>
    </html>
  )
}
```

The text aligns to the start edge, the `startSlot`/`endSlot` adornments swap sides, and the resize handle moves to the start corner to follow the resolved direction. For setup details and caveats, see the [RTL guide](/ui/docs/react/rtl).

<RtlPreview component="textarea" />

## API reference [#api-reference]

The `variant`, `inputSize`, `clearable`, `startSlot`, `endSlot`, and `onClear` props are Appica additions; every other prop is forwarded to the underlying `<textarea>`.

| Prop           | <ColMinWidth width="200">Type</ColMinWidth> | Default     | <ColMinWidth width="220">Description</ColMinWidth>                                          |
| -------------- | ------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------- |
| `variant`      | <Code>'outline' \| 'soft'</Code>            | `'outline'` | Field appearance — bordered or filled.                                                      |
| `inputSize`    | <Code>'sm' \| 'md' \| 'lg'</Code>           | `'md'`      | Scales padding and text. Named `inputSize` to avoid colliding with native attributes.       |
| `rows`         | `number`                                    | `3`         | Initial height in lines; also the resize floor.                                             |
| `startSlot`    | `ReactNode`                                 | —           | Adornment rendered before the field, aligned to the first line.                             |
| `endSlot`      | `ReactNode`                                 | —           | Adornment rendered after the field, aligned to the first line.                              |
| `clearable`    | `boolean`                                   | `false`     | Show a clear (✕) button once the field has a value.                                         |
| `onClear`      | <Code>() => void</Code>                     | —           | Called when the clear button is pressed.                                                    |
| `aria-invalid` | <Code>boolean \| 'true' \| 'false'</Code>   | —           | Marks the error state; mirrored to `data-invalid`.                                          |
| `disabled`     | `boolean`                                   | `false`     | Disables the field and removes it from the tab order.                                       |
| `placeholder`  | `string`                                    | `' '`       | Placeholder text.                                                                           |
| `value`        | `string`                                    | —           | Controlled value. Pair with `onChange`.                                                     |
| `defaultValue` | `string`                                    | —           | Uncontrolled initial value.                                                                 |
| `ref`          | <Code>Ref\<HTMLTextAreaElement></Code>      | —           | Ref to the underlying `<textarea>`.                                                         |
| `className`    | `string`                                    | —           | Extra classes, merged via `tailwind-merge` (lands on the wrapper when adornments are used). |

`Textarea` is a native `<textarea>` styled with the shared input appearance, and forwards every remaining native `<textarea>` prop (`name`, `onChange`, `maxLength`, `aria-*`, …). It exposes `data-disabled` / `data-invalid` for styling.

## Accessibility [#accessibility]

* Renders a native `<textarea>` (`role="textbox"`, multiline). Give it an accessible name — an external `<label htmlFor>` pointing at its `id`, or an `aria-label`.
* The clear button is labelled **Clear input**, kept out of the tab order (`tabIndex={-1}`), and appears only when the field has a value.
* `aria-invalid` is bridged to `data-invalid` so the error state styles correctly.
* `disabled` removes the field from the tab order and exposes `data-disabled` for styling.
