# Textarea

> A native multi-line text field with a label, description, status message and counter.

Source: /docs/components/textarea

```tsx
import { Textarea } from '@aksornproject/aksara-ui'
```

## Labelled field

`label` renders a `<label>` linked to the textarea, with a generated id unless you pass `id`. `description` renders under it. `required` sets native `required` and draws a star after the label. The native textarea owns `value`, `defaultValue`, `onChange`, validation and form submission, and `ref` is the `HTMLTextAreaElement`.

```tsx
import { Textarea } from '@aksornproject/aksara-ui'

export default function TextareaBasic() {
  return (
    <div className="w-full max-w-spacing-384">
      <Textarea
        label="Feedback"
        description="Tell us what to improve in this lesson."
        name="feedback"
        placeholder="Enter your feedback"
        required
      />
    </div>
  )
}
```

## Status and message

`status` takes `'error'`, `'warning'` or `'success'` and colours the border. `'default'` is the same as no status. `statusMessage` renders under the box in the status colour, with its glyph. `'error'` also sets `aria-invalid`, unless you pass your own.

```tsx
import { Textarea } from '@aksornproject/aksara-ui'

export default function TextareaStatus() {
  return (
    <div className="flex w-full max-w-spacing-384 flex-col gap-spacing-16">
      <Textarea
        label="Reason for absence"
        status="error"
        statusMessage="Enter a reason before you submit."
      />
      <Textarea
        label="Lesson summary"
        defaultValue="Students practised reading short Thai poems aloud."
        status="success"
        statusMessage="Saved."
      />
    </div>
  )
}
```

## Counter

`counter` is app-owned text at the end of the row under the box. The message grows to fill the row and the counter stays right-aligned. In a narrow field the row wraps. The component does not count anything, and `maxLength` passes through to the textarea unchanged.

```tsx
import { Textarea } from '@aksornproject/aksara-ui'
import { useState } from 'react'

const LIMIT = 200

export default function TextareaCounter() {
  const [value, setValue] = useState('')

  return (
    <div className="w-full max-w-spacing-384">
      <Textarea
        label="Note to parents"
        value={value}
        onChange={(event) => setValue(event.target.value)}
        maxLength={LIMIT}
        statusMessage="Parents see this note in the app."
        counter={`${value.length} / ${LIMIT}`}
      />
    </div>
  )
}
```

## Size and resizing

The textarea is 120px tall at least and 320px at most at the default scale, and longer content scrolls. It does not grow as you type. An editable textarea resizes vertically with the browser's grip. iOS may not show the grip. `className` and `style` reach the textarea, so a height or `resize` class you pass wins.

```tsx
import { Textarea } from '@aksornproject/aksara-ui'

const PLAN = Array.from(
  { length: 12 },
  (_, index) => `Week ${index + 1}: reading practice and a short quiz.`,
).join('\n')

export default function TextareaResize() {
  return (
    <div className="w-full max-w-spacing-384">
      <Textarea label="Term plan" defaultValue={PLAN} />
    </div>
  )
}
```

## States

`disabled` keeps native disabled behaviour, so the value is left out of form data. `readOnly` uses a grey surface. `loading` sets `readOnly` and `aria-busy` and shows a spinner at the end of the first line. The value, label, description, message and counter stay visible while loading. All three states turn off resizing.

```tsx
import { Textarea } from '@aksornproject/aksara-ui'

export default function TextareaStates() {
  return (
    <div className="flex w-full max-w-spacing-384 flex-col gap-spacing-16">
      <Textarea
        label="Disabled"
        defaultValue="Submissions closed on 30 June."
        disabled
      />
      <Textarea
        label="Read-only"
        defaultValue="Graded by Kru Malee on 2 July."
        readOnly
      />
      <Textarea
        label="Loading"
        defaultValue="Checking your answer for spelling."
        loading
      />
    </div>
  )
}
```

## Start icon

`startIcon` draws a decorative 20px icon before the first line of text. There is no default icon.

```tsx
import { IconInterfaceEssentialEditLine } from '@aksornproject/aksara-icons'
import { Textarea } from '@aksornproject/aksara-ui'

export default function TextareaStartIcon() {
  return (
    <div className="w-full max-w-spacing-384">
      <Textarea
        label="Answer"
        placeholder="Write your answer"
        startIcon={<IconInterfaceEssentialEditLine />}
      />
    </div>
  )
}
```

## Accessibility

- `aria-describedby` lists your own ids first, then the description, the message and the counter.
- The required star is `aria-hidden`. The textarea carries native `required`.
- The start icon and the spinner are `aria-hidden`.
- No live region is added. Announce validation messages yourself if they need it.
- The focus ring draws when the textarea has keyboard focus.

## API

### Textarea

`className`, `style`, events and `ref` go to the `<textarea>`. `fieldClassName` targets the bordered box and `wrapperClassName` the whole field. `children` is not accepted. Use `value` or `defaultValue`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label` | `ReactNode` |  | Rendered in a `<label>` linked to the textarea. |
| `description` | `ReactNode` |  | Rendered under the label and linked with `aria-describedby`. |
| `status` | `"default" \| "error" \| "success" \| "warning"` |  | Colours the border and the message. `'error'` also sets `aria-invalid`. `'default'` is the same as no status. |
| `statusMessage` | `ReactNode` |  | Rendered under the box in the `status` colour, with its glyph. Linked with `aria-describedby`. |
| `counter` | `ReactNode` |  | App-owned text at the end of the row under the box, such as `12 / 2,000`. Linked with `aria-describedby`. |
| `startIcon` | `ReactNode` |  | A decorative 20px icon on the first line, before the text. |
| `loading` | `boolean` | `false` | Keeps the value, sets `readOnly` and `aria-busy`, and shows a spinner on the first line. |
| `fieldClassName` | `string` |  | Classes for the bordered box around the textarea, merged last. |
| `wrapperClassName` | `string` |  | Classes for the outer field stack, merged last. |