<form-dropzone>

A progressively enhanced file-field wrapper that adds drag-and-drop, accessible announcements, localization, and optional image previews. See the README for installation and API documentation.

Try it: Click anywhere in an enhanced zone or drop files onto it. The original label and file input remain available as the no-JavaScript and unsupported-browser fallback.

Basic single-file picker

The only required content is one file input and its associated label.

Any file type

Without accept or multiple, the first dropped file is selected and any additional files are rejected with an accessible announcement.

<form-dropzone>
  <label for="basic-file">Choose a file</label>
  <input
    id="basic-file"
    name="basic-file"
    type="file" />
</form-dropzone>
Selected: No files

Multiple files and accept

Dropped files are checked against extension, exact MIME, and wildcard MIME accept tokens.

Mixed accept syntax

This input accepts PDF filenames, plain text MIME types, and any image MIME type. A mixed drop keeps the valid files and announces each rejection.

<form-dropzone>
  <label for="mixed-files">Choose documents or images</label>
  <input
    id="mixed-files"
    name="mixed-files"
    type="file"
    accept=".pdf,text/plain,image/*"
    multiple />
</form-dropzone>
Selected: No files

Image previews

The boolean preview-images attribute renders image selections from the picker or a drop in a responsive Light DOM grid.

Multiple image preview grid

Non-image files can remain selected when allowed by the input, but only image MIME types are previewed.

<form-dropzone preview-images>
  <label for="preview-images">Choose images</label>
  <input
    id="preview-images"
    name="preview-images"
    type="file"
    accept="image/*"
    multiple />
</form-dropzone>
Selected: No files

Localization and custom announcements

Visible instructions and all drop-result messages can be translated independently. Announcement templates support {count} and {files}.

Custom English announcements

Drop multiple PDFs plus a non-PDF file to produce received, file-type rejection, and single-file limit messages in one announcement. The visible log mirrors the component's visually hidden live region for this demo.

The default announcement templates are:

  • received-message="Received: {files}."
  • rejected-type-message="Rejected because the file type is not accepted: {files}."
  • rejected-multiple-message="Rejected because only one file is allowed: {files}."
<form-dropzone
  received-message="Ready to upload {count}: {files}."
  rejected-type-message="Not accepted: {files}. Choose a PDF."
  rejected-multiple-message="Only the first PDF was added. Extra: {files}.">
  <label for="custom-announcements">Choose one PDF</label>
  <input
    id="custom-announcements"
    name="custom-announcements"
    type="file"
    accept=".pdf" />
</form-dropzone>
Selected: No files
Latest announcement: None yet

French text and messages

Drop more than one PDF to hear both the received and single-file rejection messages.

<form-dropzone
  drop-label="Déposez les fichiers ici"
  separator-label="ou"
  received-message="{count} fichier(s) reçu(s) : {files}."
  rejected-type-message="Type refusé : {files}."
  rejected-multiple-message="Un seul fichier est autorisé. Refusé : {files}.">
  <label for="french-pdf">Choisir un PDF</label>
  <input
    id="french-pdf"
    name="french-pdf"
    type="file"
    accept=".pdf" />
</form-dropzone>
Sélection : Aucun fichier

Wrapping label association

The native label may use for/id or wrap the file input directly.

Input nested in its label

This pattern remains fully functional before custom element registration and when drag-and-drop is unavailable.

<form-dropzone>
  <label>
    Choose a spreadsheet
    <input
      name="spreadsheet"
      type="file"
      accept=".csv,.xlsx" />
  </label>
</form-dropzone>

Custom Light DOM styling

Default rules use low-specificity selectors, so normal author CSS can override alignment, borders, drag state, and preview elements.

Left-aligned circular previews

This example uses the enhancement state class and generated Light DOM preview classes.

<style>
  .custom-dropzone {
    align-items: start;
    min-block-size: 10rem;
    text-align: start;
  }

  .custom-dropzone input[type="file"] {
    background: transparent;
    border: 0;
    padding: 0;
  }

  .custom-dropzone input[type="file"]::file-selector-button {
    background: #4f3ca7;
    border: 0;
    border-radius: 0.5rem;
    color: white;
    cursor: pointer;
    font: inherit;
    margin-inline-end: 0.75rem;
    padding: 0.65rem 1rem;
  }

  .custom-dropzone input[type="file"]::file-selector-button:hover {
    background: #392b7a;
  }

  .custom-dropzone .form-dropzone__preview-image {
    border-radius: 50%;
    max-block-size: 200px;
    max-inline-size: 200px;
  }
</style>

<form-dropzone
  class="custom-dropzone"
  preview-images>
  <label for="styled-images">Choose profile images</label>
  <input
    id="styled-images"
    name="styled-images"
    type="file"
    accept="image/*"
    multiple />
</form-dropzone>
Selected: No files

API reference

For complete attribute, property, styling-hook, progressive-enhancement, and browser-support documentation, see the README.