# Input

Text input wrapping a native <input>. Supports labels, descriptions, adornments, 2 variants, and native input types.

- Package: `@mielui/svelte`
- Component version: `5.0.0`
- Depends on Mielui components: _internal/utils
- Shared utilities: components/_internal/field-metadata, utils.cn

## Install

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

## 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.


### Input

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| placeholder | string \| undefined | - | No | No | Text shown while there is no value. |
| label | string \| undefined | - | No | No | Visible label linked to the control. |
| description | string \| undefined | - | No | No | Supporting text under the label, announced with the control. |
| variant | "outline" \| "secondary" \| undefined | 'outline' | No | No | `outline` has a border. `secondary` has a filled background. |
| class | (string & (ClassValue \| null)) \| undefined | - | No | No | Classes added to the element. |
| leading | Snippet<[]> \| undefined | - | No | No | Decorative content inside the field, before the text. |
| trailing | Snippet<[]> \| undefined | - | No | No | Decorative content inside the field, after the text. |
| element | HTMLInputElement \| undefined | - | No | Yes | Bindable reference to the DOM element. |
| type | "file" \| "checkbox" \| "radio" \| "button" \| "color" \| "date" \| "datetime-local" \| "email" \| "hidden" \| "image" \| "month" \| "number" \| "password" \| "range" \| "reset" \| "search" \| "submit" \| "tel" \| "text" \| "time" \| "url" \| "week" \| undefined | 'text' | No | No | Native input type. |
| files | FileList \| undefined | - | No | Yes | Selected files for a file input. Bindable. |
| value | string \| number \| undefined | - | No | Yes | Field value. Bindable. |
| checked | boolean \| undefined | - | No | Yes | Checked state for the checkbox and radio types. Bindable. |

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

### Public types

~~~~ts
import type { Snippet } from 'svelte';
import type { HTMLInputAttributes, HTMLInputTypeAttribute } from 'svelte/elements';
import Input from './input.svelte';

type KnownInputType<Type> = Type extends string ? (string extends Type ? never : Type) : never;

export type InputProps = {
    /** Text shown while there is no value. */
    placeholder?: string;
    /** Visible label linked to the control. */
    label?: string;
    /** Supporting text under the label, announced with the control. */
    description?: string;
    /** `outline` has a border. `secondary` has a filled background. */
    variant?: 'outline' | 'secondary';
    /** Classes added to the element. */
    class?: string;
    /** Decorative content inside the field, before the text. */
    leading?: Snippet;
    /** Decorative content inside the field, after the text. */
    trailing?: Snippet;
    /** Bindable reference to the DOM element. */
    element?: HTMLInputElement | undefined;
} & Omit<HTMLInputAttributes, 'type' | 'value' | 'checked' | 'files'> &
    (
        | { type: 'file'; files?: FileList; value?: never; checked?: never }
        | { type: 'checkbox' | 'radio'; value?: string | number; checked?: boolean; files?: never }
        | {
              /** Native input type. */
              type?: Exclude<KnownInputType<HTMLInputTypeAttribute>, 'file' | 'checkbox' | 'radio'>;
              /** Field value. Bindable. */
              value?: string | number;
              /** Checked state for the checkbox and radio types. Bindable. */
              checked?: never;
              /** Selected files for a file input. Bindable. */
              files?: never;
          }
    );

export { Input };
export default Input;
~~~~

## Examples

### Adornments

~~~~svelte
<script lang="ts">
    import { GitBranchIcon as GitBranch } from '@hugeicons/core-free-icons';
    import { Input } from '@mielui/svelte/components/input';
    import HugeiconsIcon from '@mielui/svelte/hugeicons-icon';
</script>

<div class="w-full max-w-xs">
    <Input label="Repository" placeholder="mielui">
        {#snippet leading()}
            <HugeiconsIcon icon={GitBranch} />
        {/snippet}
        {#snippet trailing()}
            <span>.git</span>
        {/snippet}
    </Input>
</div>
~~~~

### Hero

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

    let projectName = $state('mielui');
    let rootDirectory = $state('./');
    let message = $state('');

    function save() {
        message =
            projectName.trim() && rootDirectory.trim()
                ? `Saved ${projectName.trim()} in ${rootDirectory.trim()}.`
                : 'Enter a project name and root directory.';
    }

    function reset() {
        projectName = 'mielui';
        rootDirectory = './';
        message = 'Changes discarded.';
    }
</script>

<div class="w-full max-w-sm space-y-6">
    <h2 class="text-sm [font-weight:var(--font-weight-label,600)] text-foreground">
        Project settings
    </h2>

    <div class="space-y-4">
        <Input
            label="Project name"
            bind:value={projectName}
            description="The name shown in your workspace."
        />

        <Input
            label="Root directory"
            placeholder="./"
            bind:value={rootDirectory}
            description="The directory where your source code lives."
        />
    </div>

    <div class="flex items-center justify-end gap-3">
        <Button variant="ghost" onclick={reset}>Reset</Button>
        <Button onclick={save}>Save settings</Button>
    </div>
    <p role="status" class="text-sm text-foreground-muted">{message}</p>
</div>
~~~~

### Validation

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

    let email = $state('');
    let workspaceSlug = $state('');
    let emailInput = $state<HTMLInputElement>();
    let workspaceSlugInput = $state<HTMLInputElement>();
    let emailTouched = $state(false);
    let workspaceSlugTouched = $state(false);
    let submitted = $state(false);

    function emailValidationMessage(
        value: string,
        input: HTMLInputElement | undefined,
        isTouched: boolean
    ): string {
        if (!isTouched) {
            return '';
        }

        if (value.trim() === '') {
            return 'Enter an email address.';
        }

        if (!input?.validity.valid) {
            return 'Enter a valid email address.';
        }

        return '';
    }

    function workspaceSlugValidationMessage(
        value: string,
        input: HTMLInputElement | undefined,
        isTouched: boolean
    ): string {
        if (!isTouched) {
            return '';
        }

        if (value.trim() === '') {
            return 'Enter a workspace slug.';
        }

        if (!input?.validity.valid) {
            return 'Use lowercase letters, numbers, and hyphens only.';
        }

        return '';
    }

    function validate(event: SubmitEvent) {
        event.preventDefault();
        emailTouched = true;
        workspaceSlugTouched = true;
        submitted = true;
    }

    let emailError = $derived(emailValidationMessage(email, emailInput, emailTouched));
    let workspaceSlugError = $derived(
        workspaceSlugValidationMessage(workspaceSlug, workspaceSlugInput, workspaceSlugTouched)
    );
    let isValid = $derived(submitted && !emailError && !workspaceSlugError);
</script>

<form class="w-full max-w-md space-y-4" novalidate onsubmit={validate}>
    <div class="space-y-1.5">
        <Input
            label="Email"
            type="email"
            placeholder="you@example.com"
            required
            bind:value={email}
            bind:element={emailInput}
            onblur={() => {
                emailTouched = true;
            }}
            aria-invalid={emailError ? 'true' : undefined}
            aria-describedby={emailError ? 'email-error email-description' : 'email-description'}
            aria-errormessage={emailError ? 'email-error' : undefined}
        />
        <div
            data-state={emailError ? 'error' : undefined}
            class="-mt-1.5 max-h-0 overflow-hidden opacity-0 transition-[margin,max-height,opacity] [transition-duration:var(--motion-duration-panel)] ease-[var(--ease-out)] motion-reduce:transition-none data-[state=error]:mt-0 data-[state=error]:max-h-24 data-[state=error]:opacity-100"
        >
            <p
                id="email-error"
                class="text-label text-[var(--mielui-error-text)]"
                role={emailError ? 'alert' : undefined}
                aria-hidden={emailError ? undefined : 'true'}
            >
                {emailError}
            </p>
        </div>
        <p id="email-description" class="text-label text-foreground-muted">
            We'll use this to send workspace invites.
        </p>
    </div>

    <div class="space-y-1.5">
        <Input
            label="Workspace slug"
            placeholder="acme-design"
            required
            pattern="[a-z0-9]+(?:-[a-z0-9]+)*"
            bind:value={workspaceSlug}
            bind:element={workspaceSlugInput}
            onblur={() => {
                workspaceSlugTouched = true;
            }}
            aria-invalid={workspaceSlugError ? 'true' : undefined}
            aria-describedby={workspaceSlugError
                ? 'workspace-slug-error workspace-slug-description'
                : 'workspace-slug-description'}
            aria-errormessage={workspaceSlugError ? 'workspace-slug-error' : undefined}
        />
        <div
            data-state={workspaceSlugError ? 'error' : undefined}
            class="-mt-1.5 max-h-0 overflow-hidden opacity-0 transition-[margin,max-height,opacity] [transition-duration:var(--motion-duration-panel)] ease-[var(--ease-out)] motion-reduce:transition-none data-[state=error]:mt-0 data-[state=error]:max-h-24 data-[state=error]:opacity-100"
        >
            <p
                id="workspace-slug-error"
                class="text-label text-[var(--mielui-error-text)]"
                role={workspaceSlugError ? 'alert' : undefined}
                aria-hidden={workspaceSlugError ? undefined : 'true'}
            >
                {workspaceSlugError}
            </p>
        </div>
        <p id="workspace-slug-description" class="text-label text-foreground-muted">
            Lowercase letters, numbers, and hyphens only.
        </p>
    </div>

    <div class="flex items-center gap-3">
        <Button type="submit">Validate fields</Button>
        {#if isValid}
            <p class="text-label text-[var(--mielui-success-text)]" role="status">
                Both fields are valid.
            </p>
        {/if}
    </div>
</form>
~~~~

### Variants

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

<div class="flex w-full max-w-xs flex-col gap-4">
    <Input label="Outline" placeholder="Placeholder text" />
    <Input variant="secondary" label="Secondary" placeholder="Placeholder text" />
</div>
~~~~

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