# Field

Composable labels, control metadata, descriptions, and validation issues.

- Package: `@mielui/svelte`
- Component version: `1.0.0`
- Depends on Mielui components: _internal/utils
- Shared utilities: utils.cn, utils.createContext, transition

## Install

~~~~sh
pnpm dlx @mielui/svelte add field
~~~~

## API

This reference is generated at build time from the component manifest, public `index.ts`, and documentation examples below. Changes to those source files are reflected here in the published Markdown. Standard Svelte and HTML attributes accepted by the exported prop types are supported.


### Content

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### Control

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| describedBy | string \| undefined | - | No | No | Ids of the description and error, for `aria-describedby`. |
| errorId | string \| undefined | - | No | No | Id of the error element. |
| children | Snippet<[FieldControlAttributes]> | - | Yes | No | Content rendered inside. |



### Description

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### Error

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| issues | readonly Readonly<{ message: string; }>[] \| undefined | - | No | No | Validation issues to list. Defaults to the Root's issues. |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### Group

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### Label

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### Root

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| controlId | string \| undefined | - | No | No | Id shared by the label and the control. |
| disabled | boolean \| undefined | false | No | No | Prevents interaction and dims the control. |
| required | boolean \| undefined | false | No | No | Requires a value before the form can be submitted. |
| invalid | boolean \| undefined | - | No | No | Marks the field invalid. It defaults to true when there are issues. |
| issues | readonly Readonly<{ message: string; }>[] \| undefined | [] | No | No | Validation issues for this field. |
| orientation | "vertical" \| "horizontal" \| undefined | 'vertical' | No | No | `vertical` stacks the label and control. `horizontal` puts them side by side. |
| element | HTMLDivElement \| undefined | - | No | Yes | Bindable reference to the DOM element. |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### Public types

~~~~ts
import type { Snippet } from 'svelte';
import type { Attachment } from 'svelte/attachments';
import type { HTMLAttributes, HTMLLabelAttributes } from 'svelte/elements';
import Root from './field.svelte';
import Content from './field-content.svelte';
import Control from './field-control.svelte';
import Description from './field-description.svelte';
import FieldError from './field-error.svelte';
import Group from './field-group.svelte';
import Label from './field-label.svelte';

export type FieldIssue = Readonly<{ message: string }>;

export type FieldControlAttributes = {
    id: string;
    disabled: boolean | undefined;
    required: boolean | undefined;
    'aria-invalid': 'true' | undefined;
    'aria-describedby'?: string;
    [attachment: symbol]: Attachment<HTMLElement>;
};

export type FieldProps = {
    /** Id shared by the label and the control. */
    controlId?: string;
    /** Prevents interaction and dims the control. */
    disabled?: boolean;
    /** Requires a value before the form can be submitted. */
    required?: boolean;
    /** Marks the field invalid. It defaults to true when there are issues. */
    invalid?: boolean;
    /** Validation issues for this field. */
    issues?: readonly FieldIssue[];
    /** `vertical` stacks the label and control. `horizontal` puts them side by side. */
    orientation?: 'vertical' | 'horizontal';
    /** Bindable reference to the DOM element. */
    element?: HTMLDivElement;
} & HTMLAttributes<HTMLDivElement>;

export type FieldControlProps = {
    /** Ids of the description and error, for `aria-describedby`. */
    describedBy?: string;
    /** Id of the error element. */
    errorId?: string;
    /** Content rendered inside. */
    children: Snippet<[FieldControlAttributes]>;
};

export type FieldLabelProps = HTMLLabelAttributes;
export type FieldDescriptionProps = HTMLAttributes<HTMLParagraphElement>;
export type FieldErrorProps = {
    /** Validation issues to list. Defaults to the Root's issues. */
    issues?: readonly FieldIssue[];
} & HTMLAttributes<HTMLDivElement>;
export type FieldContentProps = HTMLAttributes<HTMLDivElement>;
export type FieldGroupProps = HTMLAttributes<HTMLDivElement>;

export { Content, Control, Description, FieldError as Error, Group, Label, Root };
~~~~

## Examples

### Composite

~~~~svelte
<script lang="ts">
    import * as Field from '@mielui/svelte/components/field';
    import * as NumberField from '@mielui/svelte/components/number-field';
    import * as OTPField from '@mielui/svelte/components/otp-field';

    const uid = $props.id();
    let seats = $state<number | undefined>(2);
    let code = $state('');
    const disabled = false;
    const required = true;
</script>

<Field.Group class="w-full max-w-sm">
    <Field.Root {disabled} {required}>
        <Field.Label>Seats</Field.Label>
        <NumberField.Root bind:value={seats} name="seats" min={1} max={12} {disabled} {required}>
            <NumberField.Group>
                <NumberField.Decrement />
                <Field.Control describedBy={`${uid}-seats-hint`}>
                    {#snippet children(control)}
                        <NumberField.Input {...control} />
                    {/snippet}
                </Field.Control>
                <NumberField.Increment />
            </NumberField.Group>
        </NumberField.Root>
        <Field.Description id={`${uid}-seats-hint`}>
            Choose between 1 and 12 seats.
        </Field.Description>
    </Field.Root>
    <Field.Root {required}>
        <Field.Label>Verification code</Field.Label>
        <Field.Control describedBy={`${uid}-code-hint`}>
            {#snippet children(control)}
                <OTPField.Root {...control} bind:value={code} name="code" length={6} />
            {/snippet}
        </Field.Control>
        <Field.Description id={`${uid}-code-hint`}>
            Enter the six digits sent to your email.
        </Field.Description>
    </Field.Root>
</Field.Group>
~~~~

### Hero

~~~~svelte
<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</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>
~~~~

### Horizontal

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

<Field.Root orientation="horizontal" class="max-w-sm gap-3">
    <Field.Control>
        {#snippet children(control)}
            <Checkbox {...control} name="updates" class="mt-0.5" />
        {/snippet}
    </Field.Control>
    <Field.Content>
        <Field.Label>Product updates</Field.Label>
        <Field.Description>A short email when there’s something useful to share.</Field.Description>
    </Field.Content>
</Field.Root>
~~~~

### Validation

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

    let showIssue = $state(true);

    const issues = $derived(
        showIssue
            ? [
                  {
                      message: 'This username is already in use.'
                  }
              ]
            : []
    );
</script>

<div class="flex w-full max-w-sm flex-col gap-5">
    <Field.Root {issues}>
        <Field.Label>Username</Field.Label>
        <Field.Description>
            Letters, numbers, and hyphens. You can change this later.
        </Field.Description>
        <Field.Control>
            {#snippet children(control)}
                <Input {...control} name="username" value="miel" autocomplete="username" />
            {/snippet}
        </Field.Control>
        <Field.Error />
    </Field.Root>
    <Button
        variant="outline"
        class="w-fit"
        onclick={() => {
            showIssue = !showIssue;
        }}
    >
        {showIssue ? 'Clear validation issue' : 'Show validation issue'}
    </Button>
</div>
~~~~

For the rendered reference, visit [/docs/components/field](/docs/components/field).
