FormField
Layout wrapper for composite form fields that need a label, helper text, and error message but cannot rely on the built-in slots of Input/Select/Textarea.
Playground
<FormField
error=""
helper="PDF, JPG, PNG — max 10 MB"
label="Document"
>
{#snippet children(ctx)}
<!--
Das Kind ist ein `<Input>`, kein rohes `<input>`: FormField reicht
`id`/`describedBy`/`invalid`/`required` an ein *beliebiges* Control
durch, und genau diesen Vertrag soll das Beispiel zeigen — aber im
eigenen Vokabular. Vorher stand hier ein `<input type="file">` mit
acht handgeschriebenen Tailwind-Klassen; wer den Schnipsel kopierte,
kopierte eine Anleitung, an der Bibliothek vorbeizubauen (und
ausgerechnet für den Fall, für den es `FileUpload` gibt).
Kein eigenes `label` am Input: Das trägt der FormField, sonst steht
es doppelt.
-->
<Input
id={ctx.id}
placeholder="Invoice 2026-07.pdf"
aria-describedby={ctx.describedBy}
aria-invalid={ctx.invalid}
required={ctx.required}
/>
{/snippet}
</FormField>01 Examples
Wrapping a custom file input
<FormField label="Document" required helper="PDF, JPG, PNG — max 10 MB">
{#snippet children({ id, describedBy, invalid, required })}
<input
{id}
type="file"
aria-describedby={describedBy}
aria-invalid={invalid}
{required}
/>
{/snippet}
</FormField>With error
<FormField label="Document" error="Required" required>
{#snippet children({ id, describedBy, invalid })}
<input {id} type="file" aria-describedby={describedBy} aria-invalid={invalid} />
{/snippet}
</FormField>02 Customization
Built-in form primitives (Input, Select, Textarea) already render their own label + helper +
error — FormField is not needed there. Use FormField only for composite controls that don't have
those slots, e.g. FileUpload, custom number-spinner
combinations, or media uploaders.
FormField is a documented deviation from the standard styling contract: as a bare layout
wrapper without a tv() config it has no unstyled mode and no preset support. Restyle it via class (the wrapper element) and slotClasses with the hand-maintained keys wrapper, label, message, and helper — e.g. a bolder label via the label key. The wrapped control keeps its own styling
API. See Customization for the
general contract.
03 Accessibility
Label association
The wrapper renders a <label for=…> linked to
the slot's id.
Description and error share one slot
Helper text gets an id referenced via aria-describedby. When an error is present the error
message takes the spot and the helper is hidden — this matches WCAG guidance to surface the
most actionable message to AT users.
Errors are announced
The error renders with role="alert", so it is
announced when the value changes during validation.
04 API Reference
Prop | Type | Default | Description | |
|---|---|---|---|---|
children required | Snippet<[FormFieldSlotContext]> | — | Snippet receiving wiring metadata (id, describedBy, invalid,
required, disabled). The wrapped control should spread or apply
these to itself for accessibility. | |
class | string | — | Extra classes merged onto the wrapper element. | |
disabled | boolean | false | Disables visual emphasis. Pass through to the slot's control as needed. | |
error | string | — | Error message shown below the control. Replaces the helper text and
propagates invalid: true to the slot for ARIA wiring. | |
helper | string | — | Helper text shown below the control. Hidden when error is present.
Named helper to match the built-in helper prop of the form primitives
(Input, Select, Toggle, …) — one vocabulary across the API seam. | |
id | string | — | Explicit HTML id for the control. Auto-generated when omitted, then
forwarded to the slot. Caller may use it to attach external <label for>. | |
label | string | — | Label rendered above the control. Auto-linked to the slot via the generated id. | |
required | boolean | false | Adds a required asterisk to the label and propagates required: true
to the slot. Does **not** apply the native required attribute —
the slot's control is responsible for that. | |
slotClasses | Partial<Record<'wrapper' | 'label' | 'message' | 'helper', string>> | — | Per-slot class overrides.
FormField has no tv() config (it is a bare layout wrapper), so this
key union is a hand-maintained literal instead of the usual
SlotNames<typeof xVariants> derivation — keep it in sync with the
elements rendered in FormField.svelte. | |
...HTMLAttributes<HTMLDivElement> inherited | HTMLAttributes | — | HTML attributes (excluding: 'children') |
05 Types
Local type definitions used by this component.
Name | Kind | Category | Used by | Description | |
|---|---|---|---|---|---|
FormFieldProps | interface | props | 0 | — | |
FormFieldSlotContext | interface | helper | 0 | Wiring metadata passed to the FormFieldProps.children snippet. |
06 Installation
Import
import { FormField } from '@urbicon-ui/blocks';