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

Installation

npx nnuikit add file-uploader

# The file list uses the Item component (optional)
npx nnuikit add item button
import { 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

PropTypeDefaultDescription
onFilesChange (files: File[]) => void-Callback triggered when files are selected or dropped.
maxFilesToUpload number1Maximum number of files allowed.
maxFileSize number10485760 (10 MB)Maximum size in bytes for each file.
acceptedFileTypes string[][".csv", ".pdf", ".xlsx"]Array of allowed file extensions.
disable booleanfalseDisables the uploader interaction.