File Uploader
Drag-and-drop file upload with built-in validation for types, sizes, and quantities. Fully keyboard accessible.
Component Source
View the source code for the file-uploader component.
$lib/components/ui/file-uploader/file-uploader.svelteInstallation
npx nnuikit add file-uploader
# The file list uses the Item component (optional)
npx nnuikit add item buttonimport { FileUploader } from "$lib/components/ui/file-uploader";
// For the file list UI (optional)
import * as Item from "$lib/components/ui/item";
import Button from "$lib/components/ui/button/button.svelte";Quick Start
The simplest usage — just the drop zone with a callback.
<script lang="ts">
import { FileUploader } from "$lib/components/ui/file-uploader";
let files = $state<File[]>([]);
</script>
<FileUploader
onFilesChange={(newFiles) => (files = [...files, ...newFiles])}
maxFilesToUpload={3}
acceptedFileTypes={[".pdf", ".csv", ".xlsx"]}
maxFileSize={10 * 1024 * 1024}
/>With File List
Combine with the Item component to show uploaded files with name, size, and a remove button.
<script lang="ts">
import { FileUploader } from "$lib/components/ui/file-uploader";
import * as Item from "$lib/components/ui/item";
import Button from "$lib/components/ui/button/button.svelte";
let files = $state<File[]>([]);
function handleRemoveFile(index: number) {
files = files.filter((_, i) => i !== index);
}
</script>
<FileUploader
onFilesChange={(newFiles) => (files = [...files, ...newFiles])}
maxFilesToUpload={5}
acceptedFileTypes={[".pdf", ".jpg", ".png"]}
/>
{#if files.length > 0}
<div class="mt-4 space-y-2">
{#each files as file, index (file.name + index)}
<Item.Root>
<Item.Content>
<Item.Title>{file.name}</Item.Title>
<Item.Description>
{(file.size / 1024).toFixed(1)} KB
</Item.Description>
</Item.Content>
<Item.Actions>
<Button variant="orphan" size="icon-sm"
onclick={() => handleRemoveFile(index)}>
✕
</Button>
</Item.Actions>
</Item.Root>
{/each}
</div>
{/if}Interactive Playground
Configure validation rules and see the generated code.
Max Files 3
Max Size (MB) 10
Accepted Types
<script lang="ts">
import { FileUploader } from "$lib/components/ui/file-uploader";
import * as Item from "$lib/components/ui/item";
import Button from "$lib/components/ui/button/button.svelte";
let files = $state<File[]>([]);
function handleRemoveFile(index: number) {
files = files.filter((_, i) => i !== index);
}
</script>
<FileUploader
onFilesChange={(newFiles) => (files = [...files, ...newFiles])}
acceptedFileTypes={[".pdf",".csv",".xlsx"]}
maxFilesToUpload={3}
maxFileSize={10485760}
/>
{#if files.length > 0}
<div class="mt-4 space-y-2">
{#each files as file, index (file.name + index)}
<Item.Root>
<Item.Content>
<Item.Title>{file.name}</Item.Title>
<Item.Description>
{(file.size / 1024).toFixed(1)} KB
</Item.Description>
</Item.Content>
<Item.Actions>
<Button
variant="orphan"
size="icon-sm"
onclick={() => handleRemoveFile(index)}
>✕</Button>
</Item.Actions>
</Item.Root>
{/each}
</div>
{/if}Examples
PDF Only (single file)
Multi-Image (up to 5)
Accessibility
Keyboard: Tab to focus, Enter/Space to open file picker
Screen reader: aria-label on input, aria-describedby links errors
Error state: role="alert" announces validation errors immediately
Focus ring: visible brand-colored ring via focus-within
Disabled: native disabled attribute on input + pointer-events-none
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
onFilesChange | (files: File[]) => void | - | Callback triggered when files are selected or dropped. |
maxFilesToUpload | number | 1 | Maximum number of files allowed. |
maxFileSize | number | 10485760 (10 MB) | Maximum size in bytes for each file. |
acceptedFileTypes | string[] | [".csv", ".pdf", ".xlsx"] | Array of allowed file extensions. |
disable | boolean | false | Disables the uploader interaction. |