# Field

Connect a form control to its label, description, and validation errors.

A field connects a form control to its label, its description and the errors it shows, and wires up `id`, `for` and `aria-describedby` for you.

```svelte title="input.svelte"
<script lang="ts">
  import { Field, FieldDescription, FieldLabel } from "@ui-sdk/svelte/field"
  import { Input } from "@ui-sdk/svelte/input"
</script>

<div data-slot="stack">
  <Field>
    <FieldLabel>Username</FieldLabel>
    <Input name="username" placeholder="hollis" autocomplete="username" />
    <FieldDescription
      >Choose a unique username for your account.</FieldDescription
    >
  </Field>
</div>
```

Press Username to focus the input, then type a name.

## Installation

```bash
npm install @ui-sdk/svelte @ui-sdk/core
```

## Composition

Use the following composition to build a `Field`:

```text
Field
├── FieldLabel
├── Input
├── FieldDescription
└── FieldError
```

## Usage

```svelte
<script lang="ts">
  import {
    Field,
    FieldDescription,
    FieldError,
    FieldLabel,
  } from "@ui-sdk/svelte/field"
  import { Input } from "@ui-sdk/svelte/input"
</script>

<Field name="username">
  <FieldLabel>Username</FieldLabel>
  <Input autocomplete="username" required />
  <FieldDescription>This is your public name.</FieldDescription>
  <FieldError />
</Field>
```

The control is one of ours, like an [Input](/docs/svelte/components/input), a [Textarea](/docs/svelte/components/textarea), a [Select](/docs/svelte/components/select) or a [Checkbox](/docs/svelte/components/checkbox), and it wires itself to the field.

The label names the control, and the description and any error that shows describe it.

Group related fields in a [field set](#field-sets):

```svelte
<FieldSet>
  <FieldLegend />
  <FieldDescription />
  <FieldGroup>
    <Field />
    <Field />
  </FieldGroup>
</FieldSet>
```

## Controls

Use an [Input](/docs/svelte/components/input) for a line of text and a [Textarea](/docs/svelte/components/textarea) for more:

```svelte
<Field>
  <FieldLabel>Feedback</FieldLabel>
  <Textarea name="feedback" />
</Field>
```

For a control we don't ship, like a native `<select>`, use `FieldControl`. It renders an `<input>`, or another element with the `render` snippet, and connects it to the field the same way. An [agent](/docs/agents) sees only our components, so it can't fill a `FieldControl`: use ours where there is one.

```svelte
<Field>
  <FieldLabel>Department</FieldLabel>
  <FieldControl name="department">
    {#snippet render({ props })}
      <select {...props}>
        <option value="design">Design</option>
        <option value="engineering">Engineering</option>
      </select>
    {/snippet}
  </FieldControl>
</Field>
```

A [Checkbox](/docs/svelte/components/checkbox), [Switch](/docs/svelte/components/switch) or [Radio Group](/docs/svelte/components/radio) inside a field uses the field's label and description. Pressing the label toggles a checkbox or switch. A radio group takes the field's label as its name, and each radio keeps a label of its own:

```svelte
<Field>
  <Switch name="mfa" />
  <FieldLabel>Two-factor authentication</FieldLabel>
  <FieldDescription>Ask for a code when you sign in.</FieldDescription>
</Field>

<Field>
  <FieldLabel>Plan</FieldLabel>
  <RadioGroup name="plan" defaultValue="monthly">
    <label>
      <Radio value="monthly" />
      Monthly
    </label>
    <label>
      <Radio value="yearly" />
      Yearly
    </label>
  </RadioGroup>
</Field>
```

## Validation

Use native constraints such as `required`, `type="email"`, and `pattern`. Errors appear after a submit attempt and update as the value changes. The first invalid control takes focus.

Leave an error empty to show the browser's message, or use `match="valueMissing"` and `match="typeMismatch"` for your own messages. Adding an error part replaces the browser's validation bubble with that message in the page.

```svelte title="validation.svelte"
<script lang="ts">
  import {
    Field,
    FieldDescription,
    FieldError,
    FieldLabel,
  } from "@ui-sdk/svelte/field"
  import { Input } from "@ui-sdk/svelte/input"
  import { useLog } from "./log"

  const log = useLog()

  function submit(event: SubmitEvent) {
    event.preventDefault()
    log(
      `Subscribed ${new FormData(event.currentTarget as HTMLFormElement).get("email")}`
    )
  }
</script>

<form data-slot="stack" onsubmit={submit}>
  <Field name="email">
    <FieldLabel>Email</FieldLabel>
    <Input type="email" required placeholder="hollis@example.com" />
    <FieldDescription>
      {#snippet children(state)}
        {state.valid ? "Looks good." : "We'll send you a confirmation link."}
      {/snippet}
    </FieldDescription>
    <FieldError match="valueMissing">Enter your email address.</FieldError>
    <FieldError match="typeMismatch"
      >Enter an email address like hollis@example.com.</FieldError
    >
  </Field>
  <Field name="website">
    <FieldLabel>Website</FieldLabel>
    <Input type="url" required placeholder="https://example.com" />
    <FieldError />
  </Field>
  <button type="submit" data-variant="solid">Subscribe</button>
</form>
```

Press Subscribe with an empty or invalid email, then correct it to clear the error.

## Errors from Your Code

Pass `invalid` when your server or form library supplies an error. `true` shows it immediately. Clear the value to return to the browser's validation.

Give this error its own text and set `match` to `true`. Named matches such as `valueMissing` still refer to native validation. `invalid={false}` hides the field's invalid state, but does not make an invalid native control pass validation.

```svelte
<Field name="email" invalid={serverError ? true : undefined}>
  <FieldLabel>Email</FieldLabel>
  <Input type="email" required onValueChange={() => (serverError = null)} />
  <FieldError match="valueMissing">Enter your email address.</FieldError>
  {#if serverError}
    <FieldError match>{serverError}</FieldError>
  {/if}
</Field>
```

## Required and Disabled

A required control marks every part with `data-required`, and the parts' state has `required`, so a label can show a mark with CSS alone.

A disabled field disables its control and marks every part with `data-disabled`, the way a [disabled field set](#disabled-field-sets) does for every field inside it.

## Character Count

Keep the text in state and show its length in the description. Set `maxlength` on the control to enforce the limit.

```svelte
<Field>
  <FieldLabel>Bio</FieldLabel>
  <Textarea bind:value={bio} maxlength={280} />
  <FieldDescription>{bio.length} / 280</FieldDescription>
</Field>
```

## Forms

The control submits its value under its own `name`, or the field's name if it has none. Reset restores the control's default and hides validation errors. Disabled controls are left out of the submitted values. See [Forms](/docs/svelte/forms) for the shared form behavior.

## Field Sets

A field set groups related fields under a legend, and disables them together.

```svelte title="field-set.svelte"
<script lang="ts">
  import {
    Field,
    FieldDescription,
    FieldGroup,
    FieldLabel,
    FieldLegend,
    FieldSet,
  } from "@ui-sdk/svelte/field"
  import { Input } from "@ui-sdk/svelte/input"
</script>

<FieldSet>
  <FieldLegend>Address</FieldLegend>
  <FieldDescription
    >We need your address to deliver your order.</FieldDescription
  >
  <FieldGroup>
    <Field name="street">
      <FieldLabel>Street address</FieldLabel>
      <Input autocomplete="street-address" />
    </Field>
    <div data-slot="grid">
      <Field name="city">
        <FieldLabel>City</FieldLabel>
        <Input autocomplete="address-level2" />
      </Field>
      <Field name="postal-code">
        <FieldLabel>Postal code</FieldLabel>
        <Input autocomplete="postal-code" />
      </Field>
    </div>
  </FieldGroup>
</FieldSet>
```

Move through the address fields. Their legend and description name the group.

`FieldSet` is a native `<fieldset>` and `FieldLegend` a native `<legend>`, so the set is named, and disabled, before scripts run. Keep the legend the first child. A legend rendered as another element through its `render` snippet, like a `<div>`, still names the set through `aria-labelledby`.

A `FieldDescription` right inside the set, outside any `Field`, describes the set, so screen readers read it as focus enters. `FieldGroup` is a `<div>` that stacks the fields, with no role and no state: the field set is what names and disables a group.

## Disabled Field Sets

Disabling the set disables its controls and nested field sets. The form leaves their values out of submissions.

```svelte
<FieldSet disabled={sameAsShipping}>
  <FieldLegend>Billing address</FieldLegend>
  <Field name="billing-street">
    <FieldLabel>Street address</FieldLabel>
    <Input autocomplete="billing street-address" />
  </Field>
</FieldSet>
```

## Groups in a Field Set

The legend names a [checkbox group](/docs/svelte/components/checkbox#group) or a [radio group](/docs/svelte/components/radio) inside the set, so the group needs no label of its own.

```svelte
<FieldSet>
  <FieldLegend>Notifications</FieldLegend>
  <CheckboxGroup name="notify" defaultValue={["mentions"]}>
    <label>
      <Checkbox value="mentions" />
      Mentions
    </label>
    <label>
      <Checkbox value="follows" />
      New followers
    </label>
  </CheckboxGroup>
</FieldSet>
```

## Custom Rendering

Every part takes a `render` snippet, to render a different element or component in its place, like a `<select>` for `FieldControl`. It receives the props to spread and the field's state:

```svelte
<FieldDescription>
  {#snippet render({ props, state })}
    <p {...props}>
      {state.invalid ? "Use letters and numbers only." : "Letters and numbers."}
    </p>
  {/snippet}
</FieldDescription>
```

The `children` snippet receives the state too. `FieldError`'s state also has the `message` for the current problem.

State is read lazily. A part re-renders for a change only if its render function or slot read that value. Each part's state is listed under [State](#state).

## Custom Controls

`useFieldControl` lets a control of your own join the field around it, the way ours do. Outside a field it returns `null`. Inside one, it returns:

- `name()`: the field's `name`, for a control that doesn't set its own.
- `disabled()`: whether the field is disabled.
- `control(id, describedBy)`: call it once in the script, with getters of the control's own `id` and `aria-describedby`. Your description comes first, then the field's. It returns a function that merges the field's attributes into the control's props: the `id`, `aria-labelledby`, `aria-describedby`, `aria-invalid`, `disabled` and the field's data attributes, and the attachment that tells the field which element is its control. The control keeps its own `data-slot`.

Spread the merged props in the markup, so the control updates when the field changes, like when its label or errors appear.

```svelte
<script lang="ts">
  import { useFieldControl } from "@ui-sdk/svelte/field"
  import type { SvelteHTMLElements } from "svelte/elements"

  let { ...rest }: SvelteHTMLElements["input"] = $props()

  const field = useFieldControl()
  const control = field?.control(
    () => rest.id,
    () => rest["aria-describedby"]
  )
  const input = $derived({
    type: "tel",
    autocomplete: "tel" as const,
    name: field?.name(),
    ...rest,
    "data-slot": "phone-input",
  })
</script>

<input {...control ? control(input) : input} />
```

```svelte
<Field>
  <FieldLabel>Phone</FieldLabel>
  <PhoneInput required />
  <FieldError />
</Field>
```

The field reads validity from the native inputs, selects and textareas inside it, so `required` and `pattern` work on the input above. A control that isn't a native input, like a row of buttons, takes part through a native input of its own: ours render one, visually hidden, beside the control.

To build a control from scratch on the primitives ours use, see [Primitives](/docs/core/kernel/define-a-primitive).

## Styling

Use `data-invalid` and `data-valid` for validation, and `data-focused`, `data-required` and `data-disabled` for the rest. Every part of a field gets the same attributes, so a label can turn red with its control.

Each part has a `data-slot`, such as `field-label`, and its state attributes are listed under [Data Attributes](#data-attributes).

## Data Attributes

### Field
`[data-slot="field"]`

- `data-disabled`: Present when the field is disabled.
- `data-invalid`: Present while the field shows as invalid: with `invalid`, or once a submit finds the control invalid.

### FieldLabel
`[data-slot="field-label"]`

- `data-disabled`: Present when the field is disabled.
- `data-invalid`: Present while the field shows as invalid: with `invalid`, or once a submit finds the control invalid.

### FieldControl
`[data-slot="field-control"]`

- `data-disabled`: Present when the field is disabled.
- `data-invalid`: Present while the field shows as invalid: with `invalid`, or once a submit finds the control invalid.

### FieldDescription
`[data-slot="field-description"]`

- `data-disabled`: Present when the field is disabled.
- `data-invalid`: Present while the field shows as invalid: with `invalid`, or once a submit finds the control invalid.

### FieldError
`[data-slot="field-error"]`

- `data-disabled`: Present when the field is disabled.
- `data-invalid`: Present while the field shows as invalid: with `invalid`, or once a submit finds the control invalid.

### FieldSet
`FieldGroup` has `data-slot="field-group"` and no state attributes.

`[data-slot="field-set"]`

- `data-disabled`: Present when the field set is disabled, by its own prop or a `<fieldset>` around it.

### FieldLegend
`[data-slot="field-legend"]`

- `data-disabled`: Present when the field set is disabled, by its own prop or a `<fieldset>` around it.

### FieldDescription in a FieldSet
`[data-slot="field-description"]`

- `data-disabled`: Present when the field set is disabled, by its own prop or a `<fieldset>` around it.

## API Reference

Props are the same as below, as Svelte props.

A field has no change events. Read its state from the parts' data attributes, or from render functions. Set `debug` to log every state change and its reason.

### Field

- `name?` (`string`): The name the control submits under, when the control doesn't set its own.
- `disabled?` (`boolean`): Disables the control, and marks every part.
- `invalid?` (`boolean`): Whether the field shows as invalid, when your code decides, like a form library or an error from the server. `true` shows its errors now, `false` shows none whatever the browser thinks. Leave it out to show the browser's own validation once a submit finds the field invalid.
- `debug?` (`boolean`): Logs every state change with its reason to the console, in development builds.

### FieldError

- `match?` (`boolean | ValidityKey`): When this error shows. `true` shows it always and `false` never. A validity flag, like `"valueMissing"`, shows it only for that problem of the browser's own validation. Without it, any problem shows it.

### FieldSet

- `disabled?` (`boolean`): Disables every control inside it, except those in its legend, and leaves them out of the form.
- `debug?` (`boolean`): Logs every state change with its reason to the console, in development builds.

### State

- `disabled` (`boolean`): Whether the field is disabled, by itself or by a `<fieldset>`.
- `required` (`boolean`): Whether the control is required, read from its `required` or `aria-required`.
- `focused` (`boolean`): Whether focus is inside the control.
- `invalid` (`boolean`): Whether the field shows as invalid: `invalid` when you pass it, else the browser's validation once shown.
- `valid` (`boolean`): Whether the browser's validation passes, once shown. Always false while you pass `invalid`.

- `disabled` (`boolean`): Whether the field is disabled, by itself or by a `<fieldset>`.
- `required` (`boolean`): Whether the control is required, read from its `required` or `aria-required`.
- `focused` (`boolean`): Whether focus is inside the control.
- `invalid` (`boolean`): Whether the field shows as invalid: `invalid` when you pass it, else the browser's validation once shown.
- `valid` (`boolean`): Whether the browser's validation passes, once shown. Always false while you pass `invalid`.
- `message` (`string`): The browser's message for the current problem. Children you pass show instead.

A `FieldDescription` receives the field's state, or only `disabled` when it describes a field set.

- `disabled` (`boolean`): Whether the field set is disabled, by itself or by a field set around it. Whether the field is disabled, by itself or by a `<fieldset>`.
- `required` (`boolean | undefined`): Whether the control is required, read from its `required` or `aria-required`.
- `focused` (`boolean | undefined`): Whether focus is inside the control.
- `invalid` (`boolean | undefined`): Whether the field shows as invalid: `invalid` when you pass it, else the browser's validation once shown.
- `valid` (`boolean | undefined`): Whether the browser's validation passes, once shown. Always false while you pass `invalid`.

Every part of a field set receives the same state.

- `disabled` (`boolean`): Whether the field set is disabled, by itself or by a field set around it.

## Accessibility

The label names the control with `aria-labelledby`, beside a native `<label for>` where the control can take one. The description and any error that shows describe it with `aria-describedby`, and an invalid control has `aria-invalid`. The tests below cover these connections and keyboard behavior.

**Labelling**

- The label names the control.
- The description describes the control.
- Clicking the label of a text field focuses it.

**A label wrapping a field**

- Names the control inside it.
- Checks its control when clicked anywhere.
- Keeps the field's description on its control.
- Names a control that renders after it.
- Keeps the part's data-slot.
- Warns in development only when a label outside a field labels nothing.
- A description and an error outside a field keep their data-slot. Web components only.
- A hidden error outside a field stays hidden whatever display its class gives. Web components only.
- An error outside a field follows its match. Web components only.
- An error outside a field shows a child that arrives after it. Web components only.

**Errors**

- Stay hidden until the form tries to submit.
- Show the browser's message when they have no text of their own.
- Count another library's form-associated element inside the field.
- Describe the control while they show.
- Update as the value changes once shown.
- Update as state set from script changes the value once shown. Web components only.
- A field marked invalid shows its error right away.
- A field marked valid never shows the browser's errors.
- A form reset hides them again.
- Errors in the page replace the browser's message, and the first invalid control takes focus.

Runs in React, Vue and web components.

In a field set:

- The legend names the group.
- A disabled field set disables the controls inside and leaves them out of the form.
- Enabling it again restores the controls.
- A field set disabled from the start disables its required fields.
- Fields in a disabled field set mark their parts disabled.

**Enhancements**

- The description describes the group.

Runs in React, Vue and web components.

In a form:

- The form submits every checked control's name and value.
- Unchecked controls submit nothing.
- A checkbox outside the form joins it with the form attribute.
- Required controls block submission.
- A form reset returns every control to its default.
- A reset returns to the default value, not to nothing.
- A disabled fieldset disables the controls inside it.
- A read-only control still submits.
- A user's change fires native input and change events on the form.

Runs in React, Vue and web components.
