Skip to main content

File dropzone

Drag-and-drop file upload built on a native file input, with type and size limits, image previews and a removable file list.

How it works

A bsFileDropzone wraps a visually hidden <input type="file">, so it is one tab stop: Enter or Space opens the file dialog, and so does a click anywhere in the zone. Files can also be dropped on it, folders included. Selected files are listed under the zone with their size and a remove button, and additions are announced to screen readers. Its value is a File[]; bind it with [(value)], [formControl], [(ngModel)] or [formField]. Set multiple to keep adding files.

Selected: 0

<div bsFileDropzone multiple aria-label="Files" [(value)]="files"></div>
<p class="mt-3 mb-0 fg-3">Selected: {{ files().length }}</p>

File types and limits

Set accept like the native attribute, with MIME types, wildcards such as image/* or extensions. Dropped files are checked against it too. maxFiles, maxFileSize and maxTotalSize (in bytes) and maxNameLength limit the selection. Files that don’t fit are left out, explained under the zone and announced, and (rejected) emits them with the reason. Images get a preview; set [thumbnails]="false" to show a file icon instead.

Up to 3 images of 2 MB each. Last rejected: 0

<div
  bsFileDropzone
  multiple
  aria-label="Photos"
  accept="image/*"
  [maxFiles]="3"
  [maxFileSize]="2_000_000"
  [maxNameLength]="40"
  [(value)]="photos"
  (rejected)="rejectedCount.set($event.length)"></div>
<p class="mt-3 mb-0 fg-3">Up to 3 images of 2 MB each. Last rejected: {{ rejectedCount() }}</p>

Single file

Without multiple, the dropzone holds one file and a new one replaces it.

HTML
<div bsFileDropzone aria-label="CV" accept=".pdf,.doc,.docx" [maxTotalSize]="5_000_000"></div>

Sizes

Set size to sm or lg to change the padding.

HTML
<div class="d-flex flex-column gap-3">
  <div bsFileDropzone size="sm" aria-label="Small dropzone"></div>
  <div bsFileDropzone aria-label="Medium dropzone"></div>
  <div bsFileDropzone size="lg" aria-label="Large dropzone"></div>
</div>

Custom content and file list

Content inside the dropzone replaces its icon and text. Export it with #dropzone="bsFileDropzone" to read isDragging() or call remove(), clear() and open(). Set [fileList]="false" to render the files yourself.

<div
  bsFileDropzone
  #dropzone="bsFileDropzone"
  multiple
  size="lg"
  aria-label="Project files"
  [fileList]="false"
  [(value)]="files">
  <span class="fs-xl fw-semibold">
    {{ dropzone.isDragging() ? 'Let go to upload' : 'Upload project files' }}
  </span>
  <span class="fg-3">Drop them here, or click to pick them from your device.</span>
</div>
@if (files().length) {
  <table class="table mt-3 mb-0">
    <thead>
      <tr>
        <th scope="col">Name</th>
        <th scope="col">Type</th>
        <th scope="col"><span class="visually-hidden">Actions</span></th>
      </tr>
    </thead>
    <tbody>
      @for (file of files(); track file) {
        <tr>
          <td>{{ file.name }}</td>
          <td>{{ file.type || 'Unknown' }}</td>
          <td class="text-end">
            <button
              type="button"
              bsButton="subtle"
              theme="danger"
              size="sm"
              (click)="dropzone.remove(file)">
              Remove <span class="visually-hidden">{{ file.name }}</span>
            </button>
          </td>
        </tr>
      }
    </tbody>
  </table>
}

Disabled

Disable the dropzone, or its form control. It leaves the tab order and ignores drops.

HTML
<div bsFileDropzone multiple disabled aria-label="Disabled dropzone"></div>

With forms

In a bsFormField, the label points to the file input, the help text describes it, and the zone turns red with its feedback once it is touched and invalid. Validators.required fails while no file is selected. The native input always holds the selected files, so with name a plain <form> can submit them, and required works with native validation in a bsValidate form.

Attach at least one file.
Up to 5 files.
<form (submit)="send($event)">
  <div bsFormField>
    <label bsFormLabel>Attachments</label>
    <div bsFileDropzone multiple [maxFiles]="5" [formControl]="attachments"></div>
    <div bsInvalidFeedback>Attach at least one file.</div>
    <small bsFormText>Up to 5 files.</small>
  </div>
  <button type="submit" bsButton theme="primary" class="mt-3" [disabled]="attachments.invalid">
    Send
  </button>
</form>
@if (submitted()) {
  <p class="mt-3 mb-0 fg-3">Sent {{ attachments.value.length }} files.</p>
}

API

BsFileDropzone

Selector: div[bsFileDropzone] · Exported as: bsFileDropzone

Inputs

NameTypeDefaultDescription
valueFile[][]The selected files. Supports [(value)].
multiplebooleanfalseAllows more than one file. Without it, a new file replaces the selected one.
acceptstring''Accepted file types: MIME types, wildcards such as image/* or extensions.
maxFilesnumber | nullnullMaximum number of files.
maxFileSizenumber | nullnullMaximum size of each file, in bytes.
maxTotalSizenumber | nullnullMaximum size of all files together, in bytes.
maxNameLengthnumber | nullnullMaximum length of a file name, in characters.
requiredbooleanfalseSets required on the native input, for native validation.
namestring | nullnullName of the native input, so a native form submits the files.
capture'user' | 'environment' | nullnullCamera to use on mobile devices.
fileListbooleantrueLists the selected files under the zone.
thumbnailsbooleantrueShows a preview of selected images in the file list.
size'sm' | 'md' | 'lg''md'Padding of the zone.
disabledbooleanfalseDisables the dropzone. Forms set this through disable().
aria-labelstring | nullnullAccessible name of the input, when it has no visible label.
removeLabelstring'Remove'Start of the remove buttons’ accessible names, followed by the file name.
fileListLabelstring'Selected files'Accessible name of the file list.
formatSize(bytes: number) => string“1.5 MB”Formats file sizes in the file list and messages.
rejectionText(rejection: BsFileRejection) => string“a.txt isn’t an accepted file type.”Text shown and announced for a file that wasn’t added.
validation'auto' | 'valid' | 'invalid' | 'none''auto'Validation styles. auto follows the bound Angular form control.

Outputs

NameTypeDescription
valueChangeFile[]Emits the files when they change.
rejectedBsFileRejection[]Emits the files left out of a selection or drop, with a reason (type, size, total-size, count or name-length) and the limit.

Methods

NameTypeDescription
addFiles(files)BsFileRejection[]Adds files as if they were dropped, and returns the rejected ones.
remove(file)voidRemoves a selected file.
clear()voidRemoves every file and clears the messages.
open()voidOpens the browser’s file dialog.
focus()voidFocuses the input.
isDragging()booleanWhether files are being dragged over the zone.
rejections()readonly BsFileRejection[]Files left out of the last selection or drop.