

# Field

Composable label, description, and error chrome. The default export is a preset (`label`, `description`, `errorMessage`, `htmlFor`). Compose `Field.Root`, `Field.Label`, `Field.Control`, `Field.Description`, and `Field.Error` when you need native constraint messages or custom controls.

Do not pass `label` on TextField inside `Field.Root` — use Field parts around a control without FieldMeta, or use TextField's `label` / `description` / `errorMessage` without Field parts.

## Examples

### Default

```tsx
import { Field } from '@var-ui/react';

<Field
  label="Custom control"
  description="Any input composes the shared field chrome."
  htmlFor="custom-range"
>
  <input id="custom-range" type="range" />
</Field>
```

```astro
---
// No @var-ui/astro Field — use core recipe markup + native control.
import { field } from '@var-ui/core';

const f = field();
---

<div class:list={[f.root]}>
  <label class:list={[f.label]} for="custom-range">Custom control</label>
  <input id="custom-range" type="range" />
  <p class:list={[f.description]}>Any input composes the shared field chrome.</p>
</div>
```

```html
<div class="var-ui-field"><label class="var-ui-field__label" for="custom-range">Custom control</label><input id="custom-range" type="range"></input><p class="var-ui-field__description">Any input composes the shared field chrome.</p></div>
```

### Parts

```tsx
import { Button, Field } from '@var-ui/react';

<form
  onSubmit={(event) => {
    event.preventDefault();
  }}
>
  <Field.Root name="amount">
    <Field.Label>Amount</Field.Label>
    <Field.Control>
      <input name="amount" required />
    </Field.Control>
    <Field.Description>In USD</Field.Description>
    <Field.Error />
  </Field.Root>
  <Button type="submit">Save</Button>
</form>
```

```astro
<!-- No Astro binding yet — use @var-ui/react -->
```

```html
<!-- No HTML demo yet — use @var-ui/react -->
```

## Validity attributes

`Field.Root` sets these attributes for CSS:

| Attribute       | When                                                                                            |
| --------------- | ----------------------------------------------------------------------------------------------- |
| `data-invalid`  | The field is invalid (controlled `invalid`, native constraint, error content, or a form error). |
| `data-valid`    | Touched or dirty, and not invalid.                                                              |
| `data-dirty`    | The value differs from the default.                                                             |
| `data-touched`  | The control has been blurred at least once.                                                     |
| `data-filled`   | The control has a non-empty value (checkbox/radio: checked).                                    |
| `data-disabled` | The control is disabled.                                                                        |

`invalid` is controlled when passed. If omitted, it is derived from native validity, `Field.Error` content, or a matching `Form` `errors` entry. `Field.Error` `forceMount` only keeps the error slot in the layout; it does not mark the field invalid.

## Props

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| children | ReactNode | no |  | The input or custom control to wrap with field chrome. |
| className | string \| undefined | no |  | Additional CSS class names merged onto the root element. |
| description | string \| undefined | no |  | Helper text shown below the control when there is no error. |
| errorMessage | string \| undefined | no |  | Validation message; when set, the field is shown in an error state. |
| htmlFor | string \| undefined | no |  | id of the wrapped control, wired to the label's `htmlFor`. |
| label | string \| undefined | no |  | Visible label rendered above the control. |

## Accessibility

Built on React Aria Components where applicable.
