# Textarea

> A multi-line text field. A CSS component with one class and no JavaScript.

The same look and states as [Input](/components/input.md), for answers longer than one line.

## How to use it

**Plain HTML**

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.40.0/src/tokens/tokens.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.40.0/src/components/css/textarea/textarea.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.40.0/src/components/css/label/label.css">
<!-- Or everything in one file: https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.40.0/dist/lotek.css -->
```

```html
<label class="lt-label" for="objective">Objective</label>
<textarea class="lt-textarea" id="objective" rows="3"
placeholder="State the objective"></textarea>


<label class="lt-label" for="fixer-note">Note for the fixer</label>
<textarea class="lt-textarea" id="fixer-note"
data-state="success">The floor plans follow by the usual drop.</textarea>


<label class="lt-label" for="terms">Terms</label>
<textarea class="lt-textarea" id="terms"
data-state="invalid" aria-invalid="true">Too short</textarea>


<label class="lt-label" for="order-log">Order log</label>
<textarea class="lt-textarea" id="order-log"
disabled>A crew has taken job 222-0483. The order is locked.</textarea>
```

**TypeScript**

```ts
import "@lo-tek/interfaces/tokens.css"
import "@lo-tek/interfaces/textarea.css"
import "@lo-tek/interfaces/label.css"
```

`lt` is used in two ways, and which one depends on whether you create the element or find it.

**If you create the markup,** spread the attributes in. That is the way in React, Astro and anything else that renders:

```tsx
import { lt } from "@lo-tek/interfaces/react"


<textarea {...lt.textarea({ state: "invalid" })} id="objective" rows={3} />
```

The import is from `/react` here, since the block is JSX. From there the keys are `className` and `htmlFor`. In Astro and anything else without React, import `@lo-tek/interfaces` and you get `class` and `for`.

**If the element is already there,** set them on it. That is the way when you enhance HTML a server or a template has sent:

```ts
import { lt, type FieldState } from "@lo-tek/interfaces"


const field = document.querySelector<HTMLTextAreaElement>("#objective")
if (!field) throw new Error("element not found")


field.addEventListener("blur", () => {
  const state: FieldState = field.value.trim().length >= 20 ? "success" : "invalid"


  lt.setAttributes(field, lt.textarea({ state }))
})
```

`state` is the same type as in [Input](/components/input.md) and [Select](/components/select.md), so the value can be passed between them. `lt.setAttributes` clears the state from the previous call, so the field does not stay red after the error is fixed.

**kotlinx.html**

```kotlin
// Once for the whole app, in the page template: every stylesheet, from the WebJar.
head { link(rel = "stylesheet", href = LotekWebJar.CSS) }
```

On a Kotlin or Java server, the same with kotlinx.html and `lo-tek-kotlinx-html`. The builder functions are in `Lt`, with an enum for each list of values:

```kotlin
import kotlinx.html.*
import no.lotek.*
import no.lotek.html.lt


fun FlowContent.order() {
    label { lt(Lt.label()); htmlFor = "objective"; +"Objective" }
    textArea(rows = "3") { lt(Lt.textarea()); id = "objective"; placeholder = "State the objective" }
    label { lt(Lt.label()); htmlFor = "fixer-note"; +"Note for the fixer" }
    textArea { lt(Lt.textarea(state = FieldState.SUCCESS)); id = "fixer-note"; +"The floor plans follow by the usual drop." }
    label { lt(Lt.label()); htmlFor = "terms"; +"Terms" }
    textArea { lt(Lt.textarea(state = FieldState.INVALID)); id = "terms"; +"Too short" }
    label { lt(Lt.label()); htmlFor = "order-log"; +"Order log" }
    textArea { lt(Lt.textarea()); id = "order-log"; disabled = true; +"A crew has taken job 222-0483. The order is locked." }
}
```

| `data-state` | Used for |
| - | - |
| *(omitted)* | Normal state |
| `invalid` | Validation has failed |
| `success` | The content is confirmed valid |

`data-state` is colour. Screen readers need `aria-invalid="true"` as well, or [Field](/components/field.md), which sets both.

## Classes you can style

| Class | What it is |
| - | - |
| `.lt-textarea` | The field. The state comes from `data-state`. |

| Variable | Default |
| - | - |
| `--lt-textarea-min-height` | `calc(var(--lt-spacing-4) * 5)` |
| `--lt-textarea-padding` | `var(--lt-spacing-2) var(--lt-spacing-3)` |
| `--lt-textarea-radius` | `var(--lt-spacing-1)` |

`--lt-textarea-min-height` is a minimum height, not a fixed height. `rows` works above it, and the user can still drag the field taller. With the default font and padding the minimum height is a little under three rows, so `rows="1"` and `rows="2"` give the same field, and only `rows="3"` is taller. If `rows` alone should decide the height, set `--lt-textarea-min-height: 0`. The width is locked, since the class sets `resize: vertical`.

See [Customising](/customising.md) for how the variables and the layer work.

## Accessibility

What a screen reader is given when the recipe above is used, step by step. It is checked through AT-SPI, the interface Orca reads, in Chromium and Firefox, on every change, by `check-accessibility-tree.ts`, and every change the check lists as a mutation must make it fail.

1. Focus `#objective`: focus on the `textbox` "Objective", not invalid.
2. Focus `#terms`: focus on the `textbox` "Terms", invalid.

- Every field needs a visible `<label>` with `for` pointing to the `id`.
- If the field has an upper limit, say so in the help text before the user types. `maxlength` alone blocks typing without explaining why.
- If you use a character counter, put it in an element with `aria-live="polite"` and let it announce only when the limit is near. Otherwise it interrupts the reading on every keystroke.
- Do not turn off `resize`. It is often the only way the user gets an overview of a long text.
