Skip to main content
Urbicon UI

FormField

Label, helper and error around a control that has none of its own.

Playground

PDF, JPG, PNG — max 10 MB
<FormField
  error=""
  helper="PDF, JPG, PNG — max 10 MB"
  label="Document"
>
  {#snippet children(ctx)}
    <input
      id={ctx.id}
      type="file"
      aria-describedby={ctx.describedBy}
      aria-invalid={ctx.invalid || undefined}
      required={ctx.required}
      class="border-border-subtle w-full rounded-md border px-3 py-2 text-sm"
    />
  {/snippet}
</FormField>

01 Examples

Reach for FormField around a control that brings no label, helper or error of its own: a native file or colour input, a widget from somewhere else. The form blocks here take label, helper and error as props, so a FormField around one of them puts a second label above the first. The children snippet hands out a FormFieldSlotContext, and each of its fields reaches the control through a line you write.

Wrapping a native file input

required puts the asterisk on the label and appears in the snippet context. Whether the control enforces anything is down to the attribute you set on it, which is the same division of labour as for the id and the description.
PDF, JPG or PNG, max 10 MB
<FormField label="Document" required helper="PDF, JPG or PNG, max 10 MB">
  {#snippet children({ id, describedBy, invalid, required })}
    <input
      {id}
      type="file"
      aria-describedby={describedBy}
      aria-invalid={invalid || undefined}
      {required}
    />
  {/snippet}
</FormField>

A native control with an error

Setting error swaps the helper for the message, points describedBy at that message instead, and flips invalid for the control to pick up. invalid is a boolean, so pass it on as invalid || undefined unless you want an aria-invalid=false sitting in the markup.
<FormField label="Brand color" error="Pick a color with more contrast">
  {#snippet children({ id, describedBy, invalid })}
    <input
      {id}
      type="color"
      aria-describedby={describedBy}
      aria-invalid={invalid || undefined}
    />
  {/snippet}
</FormField>

02 Customization

FormField takes class on its outer element and slotClasses keyed by wrapper, label, requiredMark, helper and message, which is the error. It takes no unstyled or preset prop and resolves no provider cascade, so its look is set at the call site and nowhere else.

requiredMark is the asterisk beside the label. Because it is a slot, a form where every field is required marks nothing — slotClasses={{ requiredMark: 'hidden' }} here, and — on the fields that do resolve a provider cascade — the same entry under defaults on a BlocksProvider.

See Customization for the class and slotClasses contract shared across blocks.

03 Accessibility

One label, one control

The <label for=…> points at the single id the snippet hands out, so a FormField labels one control. Two controls in one field want a <fieldset> with a legend instead, and a control that is not a labelable element wants aria-labelledby pointing at your own label.

Errors are announced

The error message renders with role="alert", so a message that appears after a failed submit is read out without the user going looking for it.

04 API Reference

10 props
10 props 1 required
Prop
Type
Default
Description

05 Types

Local type definitions used by this component.

4 types
Name
Kind
Category
Used by
Description

06 Installation

Import

import { FormField } from '@urbicon-ui/blocks';