Skip to main content

File Upload Hds

<sl-file-upload-hds> | SlFileUploadHds
Since 2.0 experimental

A file uploader allows users to upload and attach one or more files. It shows the upload progress and status of each file.

Component Overview

The sl-file-upload component is a Shoelace.js file uploader designed to simplify file selection and management in web applications. It supports both drag-and-drop and traditional file input, and it provides a reactive design that automatically updates the user interface as file states change.

Key Features

  • Multiple File Support:
    Allows users to select or drag-and-drop multiple files, with built-in limits for file count and size.

  • Event-Driven Updates:
    The component emits events when files are added or removed, making it easy for application developers to hook into these actions.

  • Reactive File Management:
    Internally, each file is represented by a FileManifest object—a wrapper that enriches the original File with additional metadata. Changes to key properties on each FileManifest (such as upload progress, status, or image preview) are automatically reflected in the UI.

The FileManifest Object

interface FileManifest {
  file: File;
  status: 'pending' | 'loading' | 'loaded' | 'error';
  progress: number;
  error?: string;
  preview?: string;
}

The FileManifest object encapsulates a single file and additional data needed for reactivity. Its key attributes include:

  • file:
    The original file selected by the user.

  • status:
    A string indicating the file’s current state (e.g., pending, loading, loaded, or error).

  • progress:
    A numeric value between 0 and 100 representing the file’s upload or processing progress. This value is managed by the application developer, allowing custom control over progress indicators.

  • error (optional):
    A string containing an error message if file processing fails.

  • preview (optional):
    An image preview (typically a data URL) for image files. This allows the component to display a thumbnail without reloading the file.

Reactivity Design

The component’s reactivity is built around the following principles:

  • Automatic UI Synchronization:
    Each FileManifest is wrapped in a reactive layer. When properties like status, progress, preview, or error are modified, the component’s UI updates automatically. For example, if an application developer adjusts the progress of a file, any progress bar in the UI will update immediately to reflect the new value.

  • Direct Developer Control:
    Developers can access the list of files through a public getter (e.g., get files() returns an array of FileManifest objects). They can modify the properties of each manifest directly, and those changes are seamlessly synchronized with the UI without additional code.

  • Efficient Removal:
    When a file manifest is removed (via a public method like removeFile()), it is detached from the reactive system. This ensures that any further changes to a removed file manifest do not trigger UI updates, maintaining both performance and a consistent state.

Helper Instance Methods

The component provides two helper instance methods to facilitate file content reading:

  • readFileAsDataURL(fileManifest: FileManifest): Promise: Reads the file as a Data URL—a Base64-encoded string with a data URL prefix. This is useful for displaying image previews or embedding file content in the UI.

  • readFileAsArrayBuffer(fileManifest: FileManifest): Promise: Reads the file as an ArrayBuffer, which represents the raw binary data. This is useful for uploading the file to a backend or for binary processing.

These methods allow developers to retrieve the file content in the format they need, without interfering with the component’s reactivity.

Summary

The sl-file-upload component combines a user-friendly interface with a reactive design. By representing each file as a FileManifest object, it provides:

  • A clear and extensible way to manage file metadata.
  • Automatic UI updates when key properties change.
  • Developer-friendly APIs to add, edit, and remove files, while keeping the internal reactivity isolated from external manipulation.

This design ensures that the file upload process is efficient, responsive, and easy to integrate into your application.

Examples

Capture event: single file being added

Open browser console

Add a file or files to this component.

Observe console log for output.

<sl-file-upload-hds id="sfuAddFile" class="my-style"></sl-file-upload-hds>

<script type="module">
  const sfuAddFile = document.querySelector("sl-file-upload-hds#sfuAddFile.my-style");

  sfuAddFile.addEventListener('sl-upload-add', (event) => {
    console.log('!! sl-upload-add emitted !!');
    console.log('event.detail.manifest', event.detail.manifest);
    console.log('== sfuAddFile.files', sfuAddFile.files);
  });
</script>

Capture event: multiple files being added

Open browser console.

Add multiple files to this component.

Observe console log for output.

<sl-file-upload-hds id="sfuAddFileMulti" class="my-style"></sl-file-upload-hds>

<script type="module">
  const sfuAddFileMulti = document.querySelector("sl-file-upload-hds#sfuAddFileMulti.my-style");

  sfuAddFileMulti.addEventListener('sl-upload-files', (event) => {
    console.log('!! sl-upload-files emitted !!');
    console.log('> event.detail.manifest', event.detail.manifests);
    console.log('> sfuAddFileMulti.files', sfuAddFileMulti.files);
    console.log('=== Q.E.D. ===');
  });
</script>

Capture event: file being removed

Open browser console,

Add a file or files to this component.

Remove a file by clicking on trash icon.

Observe console log for output.

<sl-file-upload-hds id="sfuRemoveFile" class="my-style"></sl-file-upload-hds>

<script type="module">
  const sfuRemoveFile = document.querySelector("sl-file-upload-hds#sfuRemoveFile.my-style");

  sfuRemoveFile.addEventListener('sl-upload-remove', (event) => {
    console.log('!! sl-upload-remove emitted !!');
    console.log('> event.detail.manifest', event.detail.manifest);
    console.log('> sfuRemoveFile.files', sfuRemoveFile.files);
    console.log('=== Q.E.D. ===');
  });
</script>

Upload - fetch API

File upload with fetch browser API

This example does not have a real uploadEndpoint. if you have a working endpoint feel free to open this example on Codepen and try out!.

<sl-file-upload-hds id="sfuFetch" class="my-style"></sl-file-upload-hds>

<script type="module">
  const uploadEndpoint = "https://your-upload-endpoint.com/upload";
  const sfuFetch = document.querySelector("sl-file-upload-hds#sfuFetch.my-style");

  // Map to store active fetch requests' AbortControllers keyed by the file manifest.
  const activeFetch = new Map();

  function uploadFile(fileManifest) {
    const fileManifest = uploadEvent.detail.manifest;
    const controller = new AbortController();
    activeFetch.set(fileManifest, controller);

    try {
      // Initiate the fetch request to upload the file.
      // Note: Fetch API doesn't provide upload progress events.
      const response = await fetch(uploadEndpoint, {
        method: "PUT",
        body: fileManifest.file,
        signal: controller.signal
      });
      activeFetch.delete(fileManifest);
      
      if (response.ok) {
        console.log("Upload successful with fetch!");
        fileManifest.status = 'loaded';
      } else {
        console.error("Upload failed:", response.statusText);
        sfuFetch.showError("Upload failed: " + response.statusText);
        fileManifest.status = 'error';
        fileManifest.error = response.statusText;
      }
    } catch (error) {
      activeFetch.delete(fileManifest);
      if (error.name === 'AbortError') {
        console.log("Upload aborted for file:", fileManifest.file.name);
      } else {
        console.error("Network error during fetch upload:", error);
        sfuFetch.showError("Network error during upload. File: " + fileManifest.file.name);
        fileManifest.status = 'error';
        fileManifest.error = "Network error";
      }
    }
  }

  function cancelUploadFile(fileManifest) {
    const controller = activeFetch.get(fileManifest);
    if (controller) {
      controller.abort();
      activeFetch.delete(fileManifest);
    }
  }

  sfuFetch.addEventListener('sl-upload-add', async (uploadEvent) => {
    uploadFile(uploadEvent.detail.manifest);
  });

  sfuFetch.addEventListener('sl-upload-remove', (removeEvent) => {
    cancelUploadFile(removeEvent.detail.manifest);
  });

</script>

Upload - XMLHttpRequest API

File upload with XMLHttpRequest (XHR) browser API provides mechanism to capture file data being loaded. This allows us to calculate progress. sl-file-upload-hds FileManifest supplies interface to set progress of each file. This example demonstrate javascript required to use XHR api to achieve file upload progress.

This example does not have a real uploadEndpoint. if you have a working endpoint feel free to open this example on Codepen and try out!.

<sl-file-upload-hds id="sfuXhr" class="my-style"></sl-file-upload-hds>

<script type="module">
  const uploadEndpoint = "https://your-upload-endpoint.com/upload";
  const sfuXhr = document.querySelector("sl-file-upload-hds#sfuXhr.my-style");

  // Map to keep track of active XHR requests, keyed by the file manifest.
  const activeXHR = new Map();

  function uploadFile(fileManifest) {
    const xhr = new XMLHttpRequest();
    activeXHR.set(fileManifest, xhr); // store the xhr associated with this file

    xhr.open("PUT", uploadEndpoint, true);

    xhr.upload.onprogress = (event) => {
      if (event.lengthComputable) {
        const progress = (event.loaded / event.total) * 100;
        fileManifest.progress = progress;
        fileManifest.status = 'loading';
        console.log(`Upload progress: ${Math.round(progress)}%`);
      }
    };

    xhr.onload = () => {
      activeXHR.delete(fileManifest); // remove xhr from map on completion
      if (xhr.status === 200) {
        console.log("Upload successful!");
        fileManifest.status = 'loaded';
      } else {
        console.error("Upload failed:", xhr.statusText);
        sfuXhr.showError("Upload failed: " + xhr.statusText);
        fileManifest.status = 'error';
        fileManifest.error = xhr.statusText;
      }
    };

    xhr.onerror = () => {
      activeXHR.delete(fileManifest);
      console.error("Network error during upload.");
      sfuXhr.showError("Network error during upload. File: " + fileManifest.file.name);
      fileManifest.status = 'error';
      fileManifest.error = "Network error";
    };

    xhr.send(fileManifest.file);
  };

  function cancelUploadFile(fileManifest) {
    const xhr = activeXHR.get(fileManifest);
    if (xhr) {
      console.log("Aborting upload for file: " + fileManifest.file.name);
      xhr.abort();
      activeXHR.delete(fileManifest);
    }
  }

  sfuXhr.addEventListener('sl-upload-add', (uploadEvent) => {
    uploadFile(uploadEvent.detail.manifest);
  });

  sfuXhr.addEventListener('sl-upload-remove', (removeEvent) => {
    cancelUploadFile(removeEvent.detail.manifest);
  });

</script>

Setting file metadata: Set error and error message

Add a file or files to this component

Wait for 3 seconds.

observe first file being set as error’ed along error message.

<sl-file-upload-hds id="sfuSetError"></sl-file-upload-hds>

<script type="module">
  const sfuSetError = document.querySelector("#sfuSetError");
  const errorMessage = 'The backend rejected this file because of its content.';

  sfuSetError.addEventListener('sl-upload-files', (event) => {
    const file0 = event.detail.manifests[0];
    setTimeout(() => {
      file0.status = 'error';
      file0.error = errorMessage;
    }, 3000);
  });
</script>

Getting file content: as Data URL

The component will emit a sl-upload-files event when a file of an appropriate type has been dropped.

Open browser console,

Add a file or files to this component.

Observe console log for output.

<sl-file-upload-hds id="sfuDataUrl"></sl-file-upload-hds>

<script type="module">
  const sfuDataUrl = document.querySelector("#sfuDataUrl");

  sfuDataUrl.addEventListener('sl-upload-files', async (event) => {
    const fileManifest = event.detail.manifests[0];
    const dataUrl = await sfuDataUrl.readFileAsDataURL(fileManifest);
    console.log("File name: ", fileManifest.file.name);
    console.log("File content Data URL: ", dataUrl);
  });
</script>

Getting file content: as Array Buffer

The component will emit a sl-upload-files event when a file of an appropriate type has been dropped.

Open browser console,

Add a file or files to this component.

Observe console log for output.

<sl-file-upload-hds id="sfuArrayBuffer"></sl-file-upload-hds>

<script type="module">
  const sfuArrayBuffer = document.querySelector("#sfuArrayBuffer");

  sfuArrayBuffer.addEventListener('sl-upload-files', async () => {
    const fileManifest = sfuArrayBuffer.files[0];
    const arrayBuffer = await sfuArrayBuffer.readFileAsArrayBuffer(fileManifest);
    console.log("File name: ", fileManifest.file.name);
    console.log("File content array buffer: ", arrayBuffer);
  });
</script>

Small File Upload

<sl-file-upload-hds id="sfuSmall" upload-component-size="small"></sl-file-upload-hds>

Large File Upload

<sl-file-upload-hds id="file-upload-22" upload-component-size="large">
  <slot name="large">
    <div>Read more on how to <a href="#c">get the most out of bulk uploading.</a></div>
  </slot>
</sl-file-upload-hds>

Disabled

  <sl-file-upload-hds disabled></sl-file-upload-hds>

Restricting allowed file types

Use the allowed-types attribute to control the allowed types. Note the single/double quote pattern. This pattern is required for the deserialisation of the array.

Use the unsupported-type-message attribute to set a custom message when an unsupported file type is uploaded.

Only SVG and PNG files are allowed here

<sl-file-upload-hds
    id="branding-logo-file-upload"
    allowed-types='["svg", "png"]'
    unsupported-type-message="Only SVG and PNG files are supported here!!!!"
    >
  <p>Only SVG and PNG files are allowed here</p>
</sl-file-upload-hds>

<script>
  (() => {
    const fileUpload = document.querySelector('#branding-logo-file-upload');

    fileUpload.addEventListener('sl-upload-files', () => {
      console.log("file uploaded", fileUpload.files);
      document.querySelector('#branding-logo-upload-complete-alert').toast();
    });
  })();
</script>

full-screen-drop

Enable full screen capture below

Drag any file onto this page and observe.

enable full screen capture
<script type="module" src="http://localhost:7001/dist/shoelace-autoloader.js"></script>
<sl-switch id="chkFullScreenDrop" size="medium"
  style="margin: var(--hds-space-2x) 0">enable full screen capture</sl-switch>
<!-- <sl-file-upload-hds full-screen-drop></sl-file-upload-hds> -->
<sl-file-upload-hds id="sfuFullScreenDrop"></sl-file-upload-hds>
<script>
  const chkFullScreenDrop = document.querySelector("sl-switch#chkFullScreenDrop");
  const sfuFullScreenDrop = document.querySelector("sl-file-upload-hds#sfuFullScreenDrop");
  chkFullScreenDrop.addEventListener(
    "sl-change",
    () => chkFullScreenDrop.checked
       ? sfuFullScreenDrop.setAttribute('full-screen-drop', true)
       : sfuFullScreenDrop.removeAttribute('full-screen-drop')
  );
</script>

Access files on the sl-file-upload component

Open browser console

Add a file or files to this component.

Observe console log for output.

<sl-file-upload-hds id="sfuAccessFiles" class="my-style"></sl-file-upload-hds>

<script type="module">

window.addEventListener('DOMContentLoaded', async () => {
  await customElements.whenDefined('sl-file-upload-hds');

  const sfuAccessFiles = document.querySelector("sl-file-upload-hds#sfuAccessFiles.my-style");
  await sfuAccessFiles.updateComplete;

  const dummyContent = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
  const fileOptions = { type: "text/plain", lastModified: Date.now() };
  const file0 = new File([dummyContent], "file0.txt", fileOptions);
  const file1 = new File([dummyContent], "file1.txt", fileOptions);
  const file2 = new File([dummyContent], "file2.txt", fileOptions);
  const file3 = new File([dummyContent], "file3.txt", fileOptions);
  const file4 = new File([dummyContent], "file4.txt", fileOptions);

  sfuAccessFiles.addFile(file0);

  // hold onto manifest1: FileManifest as addFile() returns it
  const manifest1 = sfuAccessFiles.addFile(file1);

  // using addFiles() adding array of files
  sfuAccessFiles.addFiles([file2, file3, file4]);

  // configure manifest metadata directly via manifest object
  manifest1.status = 'loading';
  manifest1.progress = 68.0;

  // accessing file manifest via sl-file-upload instance
  sfuAccessFiles.files[2].status = 'error';
  sfuAccessFiles.files[2].error = 'Network error, unable to upload.';
  sfuAccessFiles.files[3].status = 'loaded';

});
</script>

[component-metadata:sl-file-upload-hds]

Slots

Name Description
(default) The default slot.
small A slot for the small component.
medium A slot for the medium component.
large A slot for the large component.
help A slot for the help text.

Learn more about using slots.

Properties

Name Description Reflects Type Default
allowedTypes
allowed-types
Allowed file types. If empty, then all supported types are allowed. string[] []
unsupportedTypeMessage
unsupported-type-message
The message to show when a file type that is not supported is dropped string ''
loadingLabel
loading-label
The label to show when loading a file string 'loading…'
maxFileSize
max-file-size
The maximum file size in MB number 5
maxFiles
max-files
The maximum number of files that can be uploaded number 10
disabled Disables the file upload boolean false
alertMessage
alert-message
Custom alert message string 'Default error message'
uploadComponentSize
upload-component-size
Display large, medium or small version of the component 'small' | 'medium' | 'large' 'medium'
fullScreenDrop
full-screen-drop
Toggle full screen drop zone boolean false
errorAlert Error container SlAlert -
fileInput The file input element HTMLInputElement -
files retrieves an array of files currently loaded onto this component. FileManifest[] -
updateComplete A read-only promise that resolves when the component has finished updating.

Learn more about attributes and properties.

Events

Name React Event Description Event Detail
sl-upload-add Emitted when a file of valid type is dropped. Event detail payload event.detail.manifest -
sl-upload-remove Emitted when a file a is removed. Event detail payload event.detail.manifest -
sl-upload-files Emitted when a list of files of valid type is dropped. Event detail payload event.detail.manifests. Note that sl-upload-add will be emitted before sl-upload-files -

Learn more about events.

Methods

Name Description Arguments
addFiles() Add an array of Files. files: File[]
addFileList() Add a FileList. files: FileList
addFile() Add an individual file. Returns FileManifest, file: File
removeFile() Remvoe an individual file (by FileManifest). manifest: FileManifest
removeFileByIndex() Remvoe an individual file by index. index: number
readFileAsDataURL() Reads the file from the provided FileManifest as a Data URL. fileManifest: FileManifest
readFileAsArrayBuffer() Reads the file from the provided FileManifest as an ArrayBuffer. fileManifest: FileManifest
showError() Show error message msg: string, mst:

Learn more about methods.

Custom Properties

Name Description Default
--example An example CSS custom property.

Learn more about customizing CSS custom properties.

Parts

Name Description
base The component’s base wrapper.

Learn more about customizing CSS parts.

Dependencies

This component automatically imports the following dependencies.

  • <sl-alert>
  • <sl-icon>
  • <sl-icon-button>