# I9kField

> Field wrapper that renders a label, hint, and error, and provides id/size/validity context to one nested control. It is the provider side of the field composable; I9kInput, I9kTextarea, and I9kSelect are its consumers.

```ts
import { I9kField } from '@9klabs/design';
```

## Examples

### Wrapping I9kInput

```vue
<I9kField label="Email" hint="We never share it."><I9kInput v-model="email" /></I9kField>
```

### Sizes

```vue
<I9kField label="Small" size="sm"><I9kInput v-model="a" /></I9kField>
<I9kField label="Medium" size="md"><I9kInput v-model="b" /></I9kField>
<I9kField label="Large" size="lg"><I9kInput v-model="c" /></I9kField>
```

### Required with error

```vue
<I9kField label="Email" required error="This field is required.">
  <I9kInput v-model="email" />
</I9kField>
```

## Watch out

- Nest exactly one control — I9kField only tracks and warns (in dev) if more than one registers.
- Inside I9kField, do not pass label, hint, error, or uiSize/size to I9kInput, I9kTextarea, or I9kSelect: I9kField supplies them through the field composable.
- I9kRadioGroup ignores I9kField entirely — it never calls useI9kField(), so wrapping it here has no effect and produces an orphaned label/hint/error alongside the group's own legend/hint/error. Pass legend, hint, error, and size straight to I9kRadioGroup instead.

## API

### Props

| Prop | Type | Default | Required |
| --- | --- | --- | --- |
| `label` | `string` | `''` | no |
| `hint` | `string` | `undefined` | no |
| `error` | `string` | `undefined` | no |
| `required` | `boolean` | `false` | no |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | no |
| `controlId` | `string` | `undefined` | no |

### Slots

- `label`
- `default`

## Agent prompt

```text
Use I9kField from @9klabs/design to wrap a single form control with a label, hint, and error.

import { I9kField } from '@9klabs/design';

Props:
- label?: string (default '') — ignored if the #label slot is used instead.
- hint?: string
- error?: string — a defined value renders the error message instead of the hint and marks the control invalid.
- required?: boolean (default false) — shows a trailing "*" next to the label.
- size?: 'sm' | 'md' | 'lg' (default 'md')
- controlId?: string — supply to pin the id instead of the auto-generated one.

Emits: none.

Slots:
- label — overrides the label prop's content.
- default — scoped slot exposing { controlId, describedBy, invalid, required, size }; the nested control reads these.

IMPORTANT: nest exactly one form control in the default slot. I9kInput, I9kTextarea, and I9kSelect read this context automatically via useI9kField() — inside I9kField, omit their own label/hint/error/uiSize props, since I9kField owns and renders those. I9kRadioGroup does NOT consume this context (it has its own legend/hint/error/size props) — do not wrap it in I9kField.

For a raw native control instead of a package component, bind the scoped slot props by hand: :id="controlId", :aria-describedby="describedBy", :aria-invalid="invalid", :required="required".

Usage:
<I9kField label="Email" hint="We never share it."><I9kInput v-model="email" /></I9kField>
```
