Studio
2

Field

Installation

pnpm dlx @mielui/svelte add field input

Usage

Field owns the relationship between a label, one control, and its feedback. The control owns its value. Spread the attributes supplied by Field.Control onto the component that renders the actual input, button, or other labelable control.

<script lang="ts">
    import * as Field from '@mielui/svelte/components/field';
    import { Input } from '@mielui/svelte/components/input';
</script>

<Field.Root class="w-full max-w-sm" required>
    <Field.Label>Work email<span class="text-foreground-muted">(required)</span></Field.Label>
    <Field.Control>
        {#snippet children(control)}
            <Input
                {...control}
                type="email"
                name="email"
                placeholder="you@company.com"
                autocomplete="email"
            />
        {/snippet}
    </Field.Control>
    <Field.Description>We’ll only use this address for account updates.</Field.Description>
</Field.Root>

Root creates a stable control ID. Label targets it even before hydration. Mounted descriptions and errors are added to the control’s aria-describedby without removing external IDs. Omit either part, reorder them, or add more than one description; no unused description ID is left behind. For a custom ID, use controlId on Root.

Control renders its snippet directly and adds no wrapper. It does not copy or clone your input. Forward the supplied attachment to the actual control when writing a custom wrapper. Input, Textarea, and components with a dedicated Input part support this pattern. When using a component that already renders a label, choose one label owner.

Server-rendered feedback

For a form that works without JavaScript, declare the description and error IDs explicitly. Pass describedBy and errorId to Control, and matching IDs to Description and Error. Control appends errorId only while Root is invalid. Keep explicitly referenced parts present whenever those IDs apply; omit the prop when you omit its part. Automatic metadata discovery enhances the mounted form but does not replace these server-rendered relationships.

<Field.Root issues={issues}>
  <Field.Label>Email</Field.Label>
  <Field.Control describedBy="email-help" errorId="email-error">
    {#snippet children(control)}
      <Input {...control} name="email" type="email" />
    {/snippet}
  </Field.Control>
  <Field.Description id="email-help">Your work address.</Field.Description>
  <Field.Error id="email-error" />
</Field.Root>

Use $props.id() to prefix these IDs when a reusable form can appear more than once.

Validation feedback

Pass issues as a readonly array of objects with a message. Root derives invalid state; Error displays the messages and removes duplicates. SvelteKit remote field issues fit this shape directly. Field does not invent validation rules, submit data, or clear your errors while someone edits.

Error reveals and collapses with the short press motion token, skips movement with reduced motion, and exposes validation text immediately. It announces local updates politely by default. When a focused form-wide summary owns error feedback, set aria-live="off" on Error to avoid repeating the same issues.

Use invalid for externally managed validity. Error can accept its own issues or custom children. Keep the Root validity state in sync when supplying a custom error. Error text describes the control; use Form.Status for a single submission announcement instead of announcing every field as an alert.

A control beside its label

Use horizontal orientation for a checkbox or switch. Content keeps the label and description together while the control remains a separate region.

Composite controls

NumberField.Root owns its value, native name, range, and stepper state. Give Field.Root and NumberField.Root the same disabled and required values, then spread Control attributes onto NumberField.Input. Field.Label replaces NumberField.Label here. OTPField.Root forwards these attributes to its real input, so spread them directly onto that root. Neither example adds a second input or duplicates the label.

Groups and required fields

Group supplies spacing between fields; it adds no ARIA role. For a named group of controls or group-wide disabled behavior, useFieldset . Root’s disabled and required props reach the control through Field.Control. Include a visible “required” or “optional” cue when the distinction matters; Field does not insert an unexplained asterisk.

Field.Root, Label, Description, Error, Content, and Group forward the native attributes for their rendered elements. Root also offers an element binding. Label renders label; Description renders p; Error, Content, Group, and Root render div. ComposeForm for pending feedback, submission, and a complete remote-form example.

API reference

Field.Root

Prop Type Default
controlId string | undefined —
disabled boolean | undefined false
required boolean | undefined false
invalid boolean | undefined —
issues readonly Readonly<{ message: string; }>[] | undefined []
orientation "horizontal" | "vertical" | undefined 'vertical'
element Bindable HTMLDivElement | undefined —

Field.Content

Field.Control

Prop Type Default
describedBy string | undefined —
errorId string | undefined —
children Required Snippet<[FieldControlAttributes]> —

Field.Description

Field.Error

Prop Type Default
issues readonly Readonly<{ message: string; }>[] | undefined —

Field.Group

Field.Label