A labelled text field: label, hint, error, unit. For hand-written forms whose fields cannot be derived from anything.
<label> + <input> pair would otherwise appear.affix, never into placeholder — a placeholder disappears the moment the user starts typing, and takes the unit with it.error turns on the error state and aria-invalid from a single text, so a red field without an explanation cannot happen.mono switches to mono and tabular-nums.IngotForm over IngotFieldSpec[]: fields nobody typed out by hand cannot drift from the schema. This component is the opposite branch — a hand-written form where there is no schema.IngotFieldInput.<dl> / <dt> / <dd>), not disabled. That one is for a field that will UNLOCK — temporarily, not forever.type: "text" | "number" | "password" | "email" | "url" | "tel" | "textarea" — Which keyboard and mask the browser offers. textarea is the same field grown to several lines.rows: number — Height of a textarea in rows. Every other type ignores it.label: ReactNode — A noun without a colon (“Quantity”).value: string — The controlled value of the input.onChange: (next: string) => void — Receives the text directly, not the event.hint: ReactNode — A full sentence with a full stop, below the field.error: ReactNode — The error text. Its presence turns on the error state and aria-invalid.affix: ReactNode — A unit or currency inside the frame (“ks”, “%”).mono: boolean — Mono and tabular-nums for codes and numbers.optionalLabel: ReactNode — A translated “— optional” next to the label.placeholder: string — An example value — never a label, never a unit.required: boolean — The input's native required.disabled: boolean — A temporarily locked field — not a way to display a value.testId: string — data-testid of the input, not the wrapper — tests reach for what is operated.