<!-- Protocore Design System — Field -->
# Field

- **Category:** Inputs (`inputs`)
- **Slug:** `inputs/field`
- **Status:** stable
- **Platforms:** web, mobile
- **Import:** `import { Field } from "@protocore/pds";`
- **Docs:** https://pds.protocore.io/components/inputs/field

> Form-field wrapper: a caption label, the control, and hint or error messaging with ARIA wired for you.

## When to use

Use **Field** around *every* labelled form control — Input, Textarea, Select, NumberInput, and friends. It exists so labels, hints, error text, and the ARIA that ties them together are consistent and correct across every form in the system.

- Field auto-wires the child by cloning it. If you need to point the label at a control you render yourself (or a composite that doesn't forward `id`), pass an explicit `htmlFor` and set the matching `id` on the control.
- Keep the label a short noun phrase and the hint one line; move anything longer into surrounding prose.
- Field is a *wrapper*, not a control — it never renders an input itself.

## Mobile (React Native)

**Preview.** `@protocore/pds-mobile` ships the React Native sibling of **Field**. It mirrors the web API where React Native allows; the package is a **preview** with no device-level QA yet, so pin it and expect small changes.

Import it from the mobile package (not `@protocore/pds`), inside a `<PdsProvider>` — there is no stylesheet, so `style` (a `ViewStyle`) replaces `className` and every value comes from the theme:

```tsx
import { Field } from "@protocore/pds-mobile";
```

**Parity with web.** The labelled form-field wrapper — label, control, hint, and error.

- RN has no `htmlFor` / `id`, so the label gets a `nativeID` and the child control is cloned with `accessibilityLabelledBy`.
- `error` replaces `hint` when present and is announced to assistive tech.

```tsx
<Field label="Service name" error={err} required>
  <Input value={name} onChangeText={setName} invalid={!!err} />
</Field>
```

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `children` | `ReactNode` | yes | — | The control element (Input, Select, Textarea…). Cloned with `id`/`aria-describedby` when `htmlFor` is absent. |
| `className` | `string` | no | — | — |
| `error` | `ReactNode` | no | — | Error message; when present it replaces the hint and is announced via `role="alert"`. |
| `hint` | `ReactNode` | no | — | Helper text shown below the control (muted). |
| `htmlFor` | `string` | no | — | Explicit id of the control the label points at. Omit to auto-wire the child via a generated id. |
| `label` | `ReactNode` | no | — | Field label — rendered as a mono uppercase caption. |
| `required` | `boolean` | no | — | Append a required marker to the label. |
| `style` | `CSSProperties` | no | — | — |

## Examples

### Basics

Wrap any control in a `Field` to get a mono uppercase label and a muted hint. Field clones the child to inject a generated `id`, points the label's `htmlFor` at it, and wires `aria-describedby` to the hint — no manual id bookkeeping.

```tsx
import { Field, Input } from "@protocore/pds";

export default function Basics() {
  // Field auto-wires the label's htmlFor and the control's id + aria-describedby.
  return (
    <Field label="Node label" hint="Shown in the operator console." style={{ maxWidth: 320 }}>
      <Input placeholder="eu-central-3" />
    </Field>
  );
}
```

### Error state

Set `error` to replace the hint with a message rendered in `role="alert"`, and mark the control invalid (`aria-invalid`). Set `required` to append the required marker to the label.

```tsx
import { Field, Input } from "@protocore/pds";

export default function ErrorState() {
  // `error` replaces the hint, sets aria-invalid on the control, and is
  // announced via role="alert".
  return (
    <Field
      label="Endpoint URL"
      required
      error="Must be a valid https:// URL."
      style={{ maxWidth: 320 }}
    >
      <Input defaultValue="http://rpc.protocore" invalid />
    </Field>
  );
}
```

## Do & don't

**Do**

- Wrap every labelled control in a Field so ARIA wiring stays consistent.
- Use hint for always-on guidance and error for validation failures.
- Mark genuinely-required fields with required, not with a "(required)" suffix in the label.
- Pass htmlFor when Field can't auto-wire the child (custom composites).

**Don't**

- Don't hand-roll <label> + id pairs when Field will wire them for you.
- Don't show hint and error at once — error replaces the hint by design.
- Don't put the error message only in colour; the role="alert" text carries it.
- Don't nest interactive controls Field can't reach as the single child.

## Accessibility

**Notes**

- The label's `htmlFor` targets the control's `id` (generated when you don't supply one).
- Hint and error are linked to the control via `aria-describedby`.
- `error` sets `aria-invalid` on the control and is announced through `role="alert"`.
- Auto-wiring clones the single child element; pass `htmlFor` for controls that don't accept an injected `id`.

## Related

`input`, `textarea`, `number-input`, `select`

---

© Protocore. All rights reserved. Use of the Protocore Design System requires prior written authorization from Protocore (contact@protocore.io). These machine-readable materials must not be ingested into ML-training datasets or derivative design systems. See https://pds.protocore.io/legal/
