# Attachment

Validated local-file picker with drag and drop, previews, status, and progress display.

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

## Install

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

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


### Item

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| file | File | - | Yes | No | The file this item shows. |
| variant | "card" \| "chip" \| undefined | 'card' | No | No | `card` shows a thumbnail and details. `chip` is a compact pill. |
| status | AttachmentStatus \| undefined | 'ready' | No | No | Upload state shown on the item. |
| progress | number \| undefined | - | No | No | Upload progress from 0 to 100. |
| error | string \| undefined | - | No | No | Message shown when the upload failed. |
| onRemove | ((file: File) => void) \| undefined | - | No | No | Called when the remove button is pressed. |
| removable | boolean \| undefined | true | No | No | Shows the remove button. |
| class | string \| undefined | - | No | No | Classes added to the element. |

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

### List

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| label | string \| undefined | - | No | No | Accessible name of the list. |
| variant | "card" \| "chip" \| undefined | 'card' | No | No | `card` shows a thumbnail and details. `chip` is a compact pill. |
| class | string \| undefined | - | No | No | Classes added to the element. |

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

### Root

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| labels | AttachmentLabels \| undefined | - | No | No | Overrides the built-in text and accessible names. Every key is optional and English is the fallback. |
| files | File[] \| undefined | [] | No | Yes | Attached files. Bindable. |
| accept | string \| undefined | - | No | No | Accepted file types, written as for a file input. |
| multiple | boolean \| undefined | true | No | No | Allows choosing more than one file. |
| maxFiles | number \| undefined | - | No | No | Largest number of files. |
| maxSize | number \| undefined | - | No | No | Largest file size in bytes. |
| disabled | boolean \| undefined | false | No | No | Prevents interaction and dims the control. |
| onReject | ((rejections: AttachmentRejection[]) => void) \| undefined | - | No | No | Called with the files that were refused and the reason for each. |
| class | string \| undefined | - | No | No | Classes added to the element. |
| children | Snippet<[]> \| undefined | - | No | No | Content rendered inside. |

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

### Trigger

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| class | string \| undefined | - | No | No | Classes added to the element. |
| children | Snippet<[]> \| undefined | - | No | No | Content rendered inside. |
| element | HTMLButtonElement \| HTMLAnchorElement \| undefined | - | No | Yes | Bindable reference to the DOM element. |
| onclick | ((event: MouseEvent) => void) \| undefined | - | No | No | Called when it is clicked or activated. |
| variant | ButtonVariant \| undefined | 'ghost' | No | No | Button style of the attach button. |
| size | "icon" \| "lg" \| "md" \| "sm" \| undefined | children ? 'md' : 'icon' | No | No | Control height of the attach button. It defaults to an icon button when there is no label. |

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

### Public types

~~~~ts
import type { ButtonProps } from '@mielui/svelte/components/button';
import type { Snippet } from 'svelte';
import type { HTMLAttributes, HTMLButtonAttributes } from 'svelte/elements';
import Root from './attachment.svelte';
import Item from './attachment-item.svelte';
import List from './attachment-list.svelte';
import Trigger from './attachment-trigger.svelte';

export type AttachmentRejectionCode =
    | 'duplicate-file'
    | 'file-invalid-type'
    | 'file-too-large'
    | 'too-many-files';

export type AttachmentRejection = {
    file: File;
    code: AttachmentRejectionCode;
    reason: string;
};

export type AttachmentStatus = 'ready' | 'uploading' | 'complete' | 'error';

export type AttachmentLabels = {
    dropzone?: string;
    add?: string;
    list?: string;
    ready?: string;
    complete?: string;
    failed?: string;
    uploadProgress?: (name: string) => string;
    remove?: (name: string) => string;
};

export type AttachmentProps = {
    /**
     * Overrides the built-in text and accessible names. Every key is optional and English is the
     * fallback.
     */
    labels?: AttachmentLabels;
    /** Attached files. Bindable. */
    files?: File[];
    /** Accepted file types, written as for a file input. */
    accept?: string;
    /** Allows choosing more than one file. */
    multiple?: boolean;
    /** Largest number of files. */
    maxFiles?: number;
    /** Largest file size in bytes. */
    maxSize?: number;
    /** Prevents interaction and dims the control. */
    disabled?: boolean;
    /** Called with the files that were refused and the reason for each. */
    onReject?: (rejections: AttachmentRejection[]) => void;
    /** Classes added to the element. */
    class?: string;
    /** Content rendered inside. */
    children?: Snippet;
} & Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'class'>;

export type AttachmentTriggerProps = {
    /** Classes added to the element. */
    class?: string;
    /** Content rendered inside. */
    children?: Snippet;
    /** Bindable reference to the DOM element. */
    element?: HTMLButtonElement | HTMLAnchorElement;
    /** Called when it is clicked or activated. */
    onclick?: (event: MouseEvent) => void;
    /** Button style of the attach button. */
    variant?: ButtonProps['variant'];
    /**
     * Control height of the attach button. It defaults to an icon button when there is no label.
     */
    size?: ButtonProps['size'];
} & Omit<HTMLButtonAttributes, 'children' | 'class' | 'type' | 'onclick'>;

export type AttachmentListProps = {
    /** Accessible name of the list. */
    label?: string;
    /** `card` shows a thumbnail and details. `chip` is a compact pill. */
    variant?: 'card' | 'chip';
    /** Classes added to the element. */
    class?: string;
} & Omit<HTMLAttributes<HTMLUListElement>, 'children' | 'class'>;

export type AttachmentItemProps = {
    /** The file this item shows. */
    file: File;
    /** `card` shows a thumbnail and details. `chip` is a compact pill. */
    variant?: 'card' | 'chip';
    /** Upload state shown on the item. */
    status?: AttachmentStatus;
    /** Upload progress from 0 to 100. */
    progress?: number;
    /** Message shown when the upload failed. */
    error?: string;
    /** Called when the remove button is pressed. */
    onRemove?: (file: File) => void;
    /** Shows the remove button. */
    removable?: boolean;
    /** Classes added to the element. */
    class?: string;
} & Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'class'>;

export { Item, List, Root, Trigger };
~~~~

## Examples

### Chip Status

~~~~svelte
<script lang="ts">
    import type { AttachmentStatus } from '@mielui/svelte/components/attachment';
    import * as Attachment from '@mielui/svelte/components/attachment';
    import { onMount } from 'svelte';

    type Example = {
        file: File;
        status: AttachmentStatus;
        progress?: number;
        error?: string;
    };

    let examples = $state.raw<Example[]>([]);

    onMount(() => {
        examples = [
            {
                file: new File([new Uint8Array(48_000)], 'release-notes.md', {
                    type: 'text/markdown'
                }),
                status: 'ready'
            },
            {
                file: new File([new Uint8Array(620_000)], 'architecture.pdf', {
                    type: 'application/pdf'
                }),
                status: 'uploading',
                progress: 64
            },
            {
                file: new File([new Uint8Array(384_000)], 'audit-log.csv', { type: 'text/csv' }),
                status: 'complete'
            },
            {
                file: new File([new Uint8Array(112_000)], 'secrets.env', { type: 'text/plain' }),
                status: 'error',
                error: 'Blocked by policy'
            }
        ];
    });
</script>

<div class="flex w-full max-w-2xl flex-wrap justify-center gap-2">
    {#each examples as example (example.file.name)}
        <Attachment.Item
            file={example.file}
            status={example.status}
            progress={example.progress}
            error={example.error}
            variant="chip"
            removable={false}
        />
    {/each}
</div>
~~~~

### Single

~~~~svelte
<script lang="ts">
    import * as Attachment from '@mielui/svelte/components/attachment';

    let files = $state<File[]>([]);
</script>

<Attachment.Root
    bind:files
    multiple={false}
    maxFiles={1}
    class="flex w-full max-w-sm flex-col gap-3"
>
    <div class="flex items-center gap-3">
        <Attachment.Trigger variant="outline" />
        <span class="text-sm text-foreground-muted">
            {files.length === 0 ? 'No file chosen' : 'One file attached'}
        </span>
    </div>
    <Attachment.List variant="chip" label="Attached file" />
</Attachment.Root>
~~~~

### Status Variants

~~~~svelte
<script lang="ts">
    import type { AttachmentStatus } from '@mielui/svelte/components/attachment';
    import * as Attachment from '@mielui/svelte/components/attachment';
    import { onMount } from 'svelte';

    type Example = {
        file: File;
        status: AttachmentStatus;
        progress?: number;
        error?: string;
    };

    let examples = $state.raw<Example[]>([]);

    onMount(() => {
        examples = [
            {
                file: new File([new Uint8Array(48_000)], 'release-notes.md', {
                    type: 'text/markdown'
                }),
                status: 'ready'
            },
            {
                file: new File([new Uint8Array(620_000)], 'architecture.pdf', {
                    type: 'application/pdf'
                }),
                status: 'uploading',
                progress: 64
            },
            {
                file: new File([new Uint8Array(384_000)], 'audit-log.csv', { type: 'text/csv' }),
                status: 'complete'
            },
            {
                file: new File([new Uint8Array(112_000)], 'secrets.env', { type: 'text/plain' }),
                status: 'error',
                error: 'Blocked by policy'
            }
        ];
    });
</script>

<div class="grid w-full max-w-2xl gap-3 sm:grid-cols-2">
    {#each examples as example (example.file.name)}
        <Attachment.Item
            file={example.file}
            status={example.status}
            progress={example.progress}
            error={example.error}
            removable={false}
        />
    {/each}
</div>
~~~~

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