Lightweight JavaScript Drag & Drop File Upload Library
Collect files from drag-and-drop or a browse dialog in plain HTML and JavaScript. hcg-file-drop is a zero-dependency widget with type filtering, a configurable file limit, duplicate detection, an optional removable file list, and a disabled state API - without React, jQuery, or any other dependency.
On this page you will find a live demo, installation (direct download, npm, CDN), a full options and API reference, React usage notes, and copy-paste examples for uploading with fetch or XMLHttpRequest.
Table of Contents
What is hcg-file-drop?
hcg-file-drop is a vanilla JavaScript widget for drag-and-drop file selection. Pass a container element and an options object; the library renders a focusable drop zone, optional file list, and returns the original File objects through onFilesChange and getFiles() - without React, jQuery, or any other dependency.
Why use hcg-file-drop?
Building a reliable drag-and-drop upload field by hand means juggling drag events, keyboard access, type validation, and DOM rendering. Use hcg-file-drop when you want a small, script-tag-friendly widget instead of a heavy upload plugin or framework wrapper.
The widget does not upload files for you. It keeps the original File objects so you can send them with FormData and fetch on your own schedule. See the comparison table for how it differs from a plain file input and larger upload libraries.
Live Demo
Drop files onto the zone below or click to browse. Accepted types: image/*, .pdf, .txt, .json. Maximum 5 files. Duplicates are skipped. Use Reset to clear the list or Disable to toggle the disabled state. For every option on one page, open the hcg-file-drop demo page.
Comparison Table
How hcg-file-drop compares with common alternatives.
| Feature | hcg-file-drop | Plain file input | Heavy upload plugins |
|---|---|---|---|
| Dependencies | None | None | Often jQuery or a framework |
| Drag and drop | Yes | No | Yes |
| Type filtering | Yes (MIME, wildcard, extension) | Limited (accept attribute) | Yes |
| Duplicate detection | Yes | No | Sometimes |
| Removable file list | Built in | No | Varies |
| Disabled state API | disable() / enable() | Attribute only | Varies |
| Uploads to server | You control upload | You control upload | Often built in |
| Approximate size | One small JS file plus CSS | n/a | Large |
| TypeScript | Definitions included | n/a | Varies |
| License / cost | Free, MIT | n/a | Varies |
Choose hcg-file-drop when you want a tiny vanilla widget that collects File objects and leaves upload logic to your app. Use a heavier plugin when you need chunked uploads, cloud storage adapters, or a full UI framework out of the box.
Features
Everything included in the library at a glance:
- Zero dependencies - one small JavaScript file and a companion CSS file.
- Script tag or bundler - works with a plain script tag, CommonJS, or ESM.
- Drag and drop or click to browse - both input methods work out of the box.
- Type filtering - accept exact MIME types, wildcards, or file extensions.
- Single or multiple - allow many files or restrict to one with the
multipleoption. - File limit - cap the number of files held at once (default 10).
- Append or replace - keep previous files on each new drop, or replace them with
replaceOnDrop. - Duplicate detection - files with the same name and size are skipped.
- Optional file list - render a removable list below the drop zone or in a custom target.
- Disabled state - start disabled, or toggle at runtime with
disable()andenable(). - Accessible - focusable zone with Enter and Space support.
- Multiple instances - independent widgets on the same page.
- TypeScript - definitions ship with the package.
- React-friendly - create in
useEffectand calldestroy()on unmount.
Installation
Basic Usage
Install hcg-file-drop, add a container element, and pass an options object. The widget renders itself inside the element and returns an instance you can call later.
Uploading Selected Files
The widget does not upload by itself. Read files with getFiles(), append each entry.file to a FormData, and send it to your server.
Using with React
Import hcgFileDrop from the package and create the widget inside useEffect. Call destroy() in the cleanup function when the component unmounts.
Try the live React demo on StackBlitz
Load hcg-file-drop.css once in your app entry or import it in the component. Use disable() and enable() from props or effects when you need to lock the widget during async work.
Multiple Instances
Create as many drop zones on a page as you need. Each call to hcgFileDrop() returns a fully independent instance with its own files, options, event listeners, and API.
<link rel="stylesheet" href="hcg-file-drop.css">
<div id="avatars"></div>
<div id="documents"></div>
<script src="hcg-file-drop.js"></script>
<script>
const avatars = hcgFileDrop(document.getElementById('avatars'), {
allowedTypes: ['image/*'],
maxFiles: 1,
showFileList: true,
onFilesChange: function (files) { console.log('avatar:', files); }
});
const docs = hcgFileDrop(document.getElementById('documents'), {
allowedTypes: ['.pdf', '.docx'],
maxFiles: 10,
showFileList: true,
onFilesChange: function (files) { console.log('docs:', files); }
});
// avatars.disable();
// docs.getFiles();
</script> - If you use
fileListTarget, give each instance a different target element. preventWindowDropacts page-wide, so you only need it on one instance.- Call each instance's
destroy()when removing it from the DOM.
Styling and Customization
All default styles live in hcg-file-drop.css, using plain single-class selectors. Edit that file directly, or override any rule from your own stylesheet loaded after it.
.hcg-dragdrop-zone- the drop zone (.hcg-dragdrop-zone--activewhile dragging over,.hcg-dragdrop-zone--disabledwhen disabled).hcg-dragdrop-hint- the hint text inside the zone.hcg-dragdrop-input- the hidden file input.hcg-dragdrop-list/.hcg-dragdrop-item- the optional file list and its rows.hcg-dragdrop-name,.hcg-dragdrop-meta,.hcg-dragdrop-remove- parts of each row
.hcg-dragdrop-zone { border-color: #10b981; background: #ecfdf5; }
.hcg-dragdrop-zone--active { border-color: #059669; }
.hcg-dragdrop-item { border-radius: 6px; } Options Reference
Pass these as the second argument to hcgFileDrop(element, options).
| Option | Type | Default | Description |
|---|---|---|---|
| allowedTypes | string[] | string | [] | Accepted MIME types or dot-extensions, as an array or comma-separated string (e.g. 'image/*, .pdf'). Supports image/png, image/*, and .pdf. Empty accepts all. |
| maxFiles | number | 10 | Maximum number of files held at once. Forced to 1 when multiple is false. |
| multiple | boolean | true | Allow selecting multiple files. When false, only a single file is accepted and each new selection replaces the current one. |
| replaceOnDrop | boolean | false | When false, new files are appended. When true, each new drop clears the old files and keeps only the new ones. |
| onFilesChange | function | - | Called whenever the accepted file list changes (add, remove, or reset). |
| showFileList | boolean | false | Render a removable file list below the drop zone. |
| fileListTarget | HTMLElement | string | - | Where to render the file list - an element, id, or CSS selector. Implies showFileList. Defaults to inside the widget element. |
| disabled | boolean | false | Start the widget in a disabled state. |
| preventWindowDrop | boolean | false | Stop the browser from navigating to a file dropped outside the zone. While enabled it also suppresses other drop targets on the page. |
Duplicate files (same name and size) are skipped automatically.
API Reference
hcgFileDrop(element, options) returns an instance with these methods. The factory also exposes hcgFileDrop.version.
| Method | Returns | Description |
|---|---|---|
| getFiles() | array | Shallow copy of the current accepted file entries. |
| reset() | void | Clears file state, empties the list, and fires onFilesChange. |
| enable() | void | Enables interaction (drag, click, browse, remove). |
| disable() | void | Disables interaction and dims the drop zone. |
| isDisabled() | boolean | Whether the widget is currently disabled. |
| destroy() | void | Removes all event listeners and injected DOM nodes. |
Accessibility
The drop zone is a focusable role="button" element with an aria-label. Users can press Enter or Space to open the file browser. When disabled, aria-disabled is set and the zone is removed from the tab order.
Pair the widget with visible labels and error messages so users know which files are accepted and what happens after selection. Do not rely on drag-and-drop alone for critical workflows.
Browser Support
Works in all modern browsers that support the File and Drag-and-Drop APIs, including the latest versions of Chrome, Firefox, Safari, and Edge. No polyfills are required.
License
Released under the MIT License. Free for personal and commercial use. Copyright HTML Code Generator.
Frequently asked questions
Does this drag and drop upload library have any dependencies?
No. It is written in plain vanilla JavaScript with zero dependencies. You only need a single script file and the companion CSS stylesheet, which works with a script tag, CommonJS, or a bundler.
Does the library upload files to a server automatically?
No. The widget collects the selected File objects and reports them through the onFilesChange callback and getFiles() method. You keep full control over how and where to upload them, for example with FormData and fetch.
How does duplicate detection work?
When a file is added, the widget compares its name and size against the files already in the list. Any file that matches an existing entry is skipped automatically.
Can the widget be disabled and re-enabled at runtime?
Yes. Call disable() to block all interaction and dim the drop zone, enable() to restore it, and isDisabled() to read the current state. You can also start disabled with the disabled option.
Does it work with React?
Yes. Create the widget inside a useEffect hook against a ref element, store the returned instance, and call destroy() in the cleanup function. See Using with React on this page.
Can I run multiple upload zones on one page?
Yes. Each call to hcgFileDrop() returns an independent instance with its own files, options, and API. Link hcg-file-drop.css once and initialize as many containers as you need.
How do I filter accepted file types?
Pass allowedTypes as an array or comma-separated string. Use exact MIME types like image/png, wildcards like image/*, or extensions like .pdf. An empty list accepts all file types.