# Form field

Form field is a neobrutalist React component from Cruise UI. Label (with an optional Required marker), the control, a hint and an error with an icon; wires the ARIA for you.

Install (with the @cruiseui registry in components.json, see Installation):

```bash
npx shadcn@latest add @cruiseui/form-field
```

Import: `import { CruiseFormField } from "@/components/cruise/form-field";`

## When to use

Around any form control: Input, Textarea, Select, Combobox, Naira and Phone inputs.

## When not to

Checkboxes, radios and switches (they carry their own label).

## Example (React)

```tsx
"use client";

import { useState } from "react";
import { CruiseCombobox } from "@/components/cruise/combobox";
import { CruiseFormField } from "@/components/cruise/form-field";
import { CruiseInput } from "@/components/cruise/input";
import { CruiseSelect } from "@/components/cruise/select";

const states = ["Lagos", "Oyo", "Rivers", "Kano", "Enugu", "FCT"].map((label) => ({
  value: label.toLowerCase(),
  label,
}));

export default function FormFieldExample() {
  const [name, setName] = useState("");
  return (
    <div className="grid gap-6 md:grid-cols-2">
      <CruiseFormField
        label="Full name"
        required
        hint="As it appears on your ID."
        error={name.length === 1 ? "Enter your full name." : undefined}
      >
        <CruiseInput value={name} onChange={(event) => setName(event.target.value)} autoComplete="name" />
      </CruiseFormField>
      <CruiseFormField label="Delivery time" hint="We'll text you when the rider leaves.">
        <CruiseSelect defaultValue="morning">
          <option value="morning">Morning (8am – 12 noon)</option>
          <option value="evening">Evening (4pm – 8pm)</option>
        </CruiseSelect>
      </CruiseFormField>
      <CruiseFormField label="State" error="Choose your state.">
        <CruiseCombobox items={states} placeholder="Search states" />
      </CruiseFormField>
    </div>
  );
}
```

## HTML

Needs theme.css (https://cruiseui.dev/theme.css).

```html
<div class="field">
  <label class="field-label" for="name">Full name <span class="field-required">Required</span></label>
  <input class="input" id="name" autocomplete="name" required aria-invalid="true" aria-describedby="name-hint name-error">
  <p class="field-help" id="name-hint">As it appears on your ID.</p>
  <p class="field-error" id="name-error">
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 8v4M12 16h.01"/></svg>
    <span>Enter your full name.</span>
  </p>
</div>
```

## Props: CruiseFormField

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label` | `ReactNode` | — | — |
| `required?` | `boolean` | `false` | Shows "Required" next to the label and marks the control required. |
| `hint?` | `ReactNode` | — | Helper text under the control. |
| `error?` | `ReactNode` | — | Error message: --danger border on the control, the message below. |
| `id?` | `string` | — | The control's id (generated when left out). |
| `className?` | `string` | — | — |
| `children` | `ReactNode` | — | — |

## Accessibility

- The label is a real <label> tied to the control's id.
- Hint and error ids go into the control's aria-describedby; an error sets aria-invalid.
- The error has an icon and words, never colour alone.
