# Text Input

A native `<input>` or `<textarea>` styled via a **single class**. Same `.x-text-input` covers three usages:

1. **Bare input** — `<input class="unset x-text-input" />`
2. **Bare textarea** — `<textarea class="unset x-text-input" />`
3. **Wrapper** — `<div class="x-text-input">` with one input/textarea and any number of affix elements

Mode is selected by what the element *is* (`:is(input, textarea)`) and what it *contains* (`:has(> input)` → horizontal row, `:has(> textarea)` → vertical column). No marker class on affixes, no `data-side`. Source order is render order.



## Install

```ts
import "elements-kit/ui/styles.css";

import "elements-kit/ui/styles/palette/gray.css";
import "elements-kit/ui/styles/neutral/gray.css";
import "elements-kit/ui/styles/palette/black-alpha.css";
import "elements-kit/ui/styles/palette/mint.css";
import "elements-kit/ui/styles/accent/mint.css";

import "elements-kit/ui/text-input/text-input.css";
```

## API

```html
<!-- Bare input -->
<input class="unset x-text-input" data-size="2" data-variant="surface" />

<!-- Bare textarea -->
<textarea class="unset x-text-input" data-size="2" data-variant="surface" />

<!-- Input wrapper — horizontal: leading/trailing affixes -->
<div class="x-text-input" data-size="2" data-variant="surface">
  <SearchIcon />
  <input class="unset" placeholder="Search…" />
  <kbd>⌘K</kbd>
</div>

<!-- Textarea wrapper — vertical: top/bottom affixes -->
<div class="x-text-input" data-size="2" data-variant="surface">
  <div>Markdown</div>
  <textarea class="unset" />
  <div>0 / 280</div>
</div>
```

| Attribute                                    | Values                                    |
| -------------------------------------------- | ----------------------------------------- |
| `data-variant`                               | `surface` (default), `soft`               |
| `data-size`                                  | `1`, `2` (default), `3`                   |
| `data-accent` *(on a parent or the element)* | any imported color scale                  |
| `disabled` / `readonly`                      | native — dims background, switches cursor |

## Sizing

| `data-size`     | input height | textarea min-height | radius                           |
| --------------- | ------------ | ------------------- | -------------------------------- |
| `1`             | `--space-5`  | `--space-8`         | `max(--radius-2, --radius-pill)` |
| `2` *(default)* | `--space-6`  | `--space-9`         | `max(--radius-2, --radius-pill)` |
| `3`             | `--space-7`  | `80px`              | `max(--radius-3, --radius-pill)` |

## Variants

* **`surface`** *(default)* — `--color-surface` background, 1px `--neutral-a7` border, `--focus-8` focus ring.
* **`soft`** — `--accent-a3` background, no border, `--accent-8` focus ring.

## Wrapper anatomy

Inside the wrapper, document order chooses where an affix lands.

```html
<div class="x-text-input">
  <span>A</span>     <!-- before input → leading (left for input, top for textarea) -->
  <input class="unset" />
  <span>B</span>     <!-- after input → trailing (right for input, bottom for textarea) -->
</div>
```

* The wrapped `<input>` / `<textarea>` has its background, border, and outline stripped — the wrapper owns the chrome. A textarea keeps its `--text-input-padding` next to top/bottom affixes; an input drops its text-indent after a leading affix. Corners shared with an affix are squared.
* Affixes get `display: flex; align-items: center; cursor: text` and inline padding of `--space-1` / `--space-2` / `--space-3` by size — override it on the affix to change or remove it.
* Focus-within outline wraps the whole container, so a focus ring lands once on the row/column rather than on the inner control.

Clicking an affix focuses the input because the affix carries `cursor: text` and the input remains the only focusable child in the row/column.

## States

* **Disabled / read-only** — `var(--neutral-a11)` text, dimmed background. When the input is empty (`:placeholder-shown`), cursor becomes `var(--cursor-disabled)` and propagates to affixes inside the wrapper.
* **Autofill** — Chrome's yellow autofill background is suppressed via `background-clip: text` while preserving the colored text.

## Accessibility

* Pair with a `<label>` (wrapping or `for=`). The wrapper does not own the label.
* The wrapper is a presentational container; the `<input>` / `<textarea>` keeps all native semantics, form participation, and ARIA.
* `aria-invalid="true"` on the input/textarea is yours to apply for error states.
