A lightweight, accessible file upload web component with drag-drop support, file previews, validation, and multiple display modes.
- Drag & Drop - Visual feedback on drag over with click-to-browse fallback
- File Validation - Max size, allowed types, max file count
- Multiple Display Modes - List, detailed, grid (with image previews), and compact
- Image Previews - Automatic thumbnail generation for image files
- RTL Support - Full right-to-left language support
- Accessible - Keyboard navigation and ARIA support
- Form Integration - Works with standard HTML forms
- Modern - Web Component with Shadow DOM, TypeScript, bundled with Vite
- Framework Agnostic - Works with any framework or vanilla JS
npm install @keenmate/web-dropzone<!-- Simple dropzone -->
<web-dropzone></web-dropzone>
<!-- With options -->
<web-dropzone
accept="image/*,.pdf"
multiple
max-file-size="5242880"
max-file-count="10"
display-mode="detailed">
</web-dropzone>// Import the component (includes styles)
import '@keenmate/web-dropzone';
// Or import styles separately if needed
import '@keenmate/web-dropzone/style.css';
const dropzone = document.querySelector('web-dropzone');
// Listen for events
dropzone.addEventListener('file-added', (e) => {
console.log('File added:', e.detail.file);
console.log('All files:', e.detail.files);
});
dropzone.addEventListener('files-rejected', (e) => {
console.log('Rejected files:', e.detail.files);
console.log('Errors:', e.detail.errors);
});
dropzone.addEventListener('change', (e) => {
console.log('Files changed:', e.detail.files);
});
// Get current files
const files = dropzone.files;
// Clear all files
dropzone.clear();| Attribute | Type | Default | Description |
|---|---|---|---|
accept |
string |
- | Allowed file types (e.g., image/*,.pdf) |
multiple |
boolean |
true |
Allow multiple file selection |
max-file-size |
number |
- | Maximum file size in bytes |
max-file-count |
number |
- | Maximum number of files |
display-mode |
'list' | 'detailed' | 'grid' | 'compact' |
'list' |
How to display selected files |
files-inside |
boolean |
false |
Show files inside dropzone area |
disabled |
boolean |
false |
Disable the dropzone |
name |
string |
- | Form field name for form integration |
value-format |
'json' | 'csv' | 'array' |
'json' |
Format for form value serialization |
drop-text |
string |
'Drop files here' |
Text shown in drop zone |
drop-hint |
string |
'or click to browse' |
Hint text below drop text |
overlay-target |
string |
- | Element ID for drag overlay (ultra-compact mode) |
overlay-text |
string |
'Drop files here' |
Text shown in drag overlay |
overlay-icon |
string |
'📎' |
Icon shown in drag overlay |
Simple list of file names with remove buttons.
<web-dropzone display-mode="list"></web-dropzone>Shows file icon, name, size, and type for each file.
<web-dropzone display-mode="detailed"></web-dropzone>Image preview grid with thumbnails. Perfect for image uploads.
<web-dropzone display-mode="grid" accept="image/*"></web-dropzone>Minimal inline display showing file count and total size. Click to open a popover with the full file list.
<web-dropzone display-mode="compact"></web-dropzone>For minimal form integration, use compact mode with overlay-target to enable a drag overlay. When users drag files anywhere over the target element, a full overlay appears for dropping files.
<form id="my-form">
<input type="text" name="title" placeholder="Title">
<web-dropzone
display-mode="compact"
overlay-target="my-form"
overlay-text="Drop files here"
overlay-icon="📎">
</web-dropzone>
<button type="submit">Submit</button>
</form>Display selected files inside the dropzone area instead of a separate list below:
<web-dropzone files-inside display-mode="list"></web-dropzone>
<web-dropzone files-inside display-mode="grid" accept="image/*"></web-dropzone>| Event | Detail | Description |
|---|---|---|
file-added |
{ file, files } |
Fired when a file is added |
file-removed |
{ file, files } |
Fired when a file is removed |
files-rejected |
{ files, errors } |
Fired when files fail validation |
change |
{ files } |
Fired when the file list changes |
const dropzone = document.querySelector('web-dropzone');
// Single file added
dropzone.addEventListener('file-added', (e) => {
const { file, files } = e.detail;
console.log(`Added: ${file.name}`);
console.log(`Total files: ${files.length}`);
});
// Files rejected (validation failed)
dropzone.addEventListener('files-rejected', (e) => {
const { files, errors } = e.detail;
files.forEach((file, index) => {
console.log(`${file.name}: ${errors[index]}`);
});
});
// Any change to file list
dropzone.addEventListener('change', (e) => {
const { files } = e.detail;
updateUI(files);
});// Get all files
const files: FileState[] = dropzone.files;
// Get/set accept types
dropzone.accept = 'image/*,.pdf';
// Get/set max size
dropzone.maxFileSize = 5242880; // 5MB
// Get/set max files
dropzone.maxFileCount = 10;
// Get/set display mode
dropzone.displayMode = 'grid';
// Check if multiple files allowed
const isMultiple = dropzone.multiple;
// Check if disabled
const isDisabled = dropzone.disabled;| Method | Description |
|---|---|
clear() |
Remove all files |
addFiles(files: FileList | File[]) |
Add files programmatically |
removeFile(id: string) |
Remove a specific file by ID |
getFile(id: string) |
Get a file by ID |
updateFileProgress(id: string, progress: number) |
Update upload progress (0-100) |
setFileStatus(id: string, status, error?) |
Set file status (pending/uploading/complete/error) |
Use updateFileProgress and setFileStatus to track upload progress:
const dropzone = document.querySelector('web-dropzone');
// Simulate upload for each file
dropzone.files.forEach(file => {
let progress = 0;
const interval = setInterval(() => {
progress += 10;
dropzone.updateFileProgress(file.id, progress);
if (progress >= 100) {
clearInterval(interval);
dropzone.setFileStatus(file.id, 'complete');
}
}, 200);
});
// Handle upload error
dropzone.setFileStatus(file.id, 'error', 'Upload failed');Each file in the files array has the following structure:
interface FileState {
id: string; // Unique identifier
file: File; // Original File object
name: string; // File name
size: number; // File size in bytes
type: string; // MIME type
status: 'pending' | 'uploading' | 'complete' | 'error';
progress: number; // Upload progress (0-100)
error?: string; // Error message if status is 'error'
preview?: string; // Data URL for image preview
}The component works with standard HTML forms:
<form id="upload-form">
<web-dropzone name="files" value-format="json"></web-dropzone>
<button type="submit">Upload</button>
</form>
<script>
document.getElementById('upload-form').addEventListener('submit', (e) => {
e.preventDefault();
const formData = new FormData(e.target);
// Get file references as JSON
const filesJson = formData.get('files');
console.log('Files:', JSON.parse(filesJson));
// Or access files directly
const dropzone = document.querySelector('web-dropzone');
const files = dropzone.files.map(f => f.file);
// Upload files
const uploadData = new FormData();
files.forEach(file => uploadData.append('files[]', file));
fetch('/upload', { method: 'POST', body: uploadData });
});
</script>json- JSON array of file names (default)csv- Comma-separated file namesarray- Multiple hidden inputs withname[]
The component uses Shadow DOM with CSS custom properties for theming.
Use --dz-rem for global scaling:
<!-- Compact (80%) -->
<web-dropzone style="--dz-rem: 8px;"></web-dropzone>
<!-- Default (100%) -->
<web-dropzone></web-dropzone>
<!-- Large (120%) -->
<web-dropzone style="--dz-rem: 12px;"></web-dropzone>| Variable | Default | Description |
|---|---|---|
--dz-accent-color |
#3b82f6 |
Primary accent color |
--dz-text-color-1 |
#111827 |
Primary text color |
--dz-text-color-2 |
#6b7280 |
Secondary text color |
--dz-border-color |
#e5e7eb |
Border color |
--dz-dropzone-bg |
#ffffff |
Dropzone background |
--dz-dropzone-bg-active |
#eff6ff |
Background when dragging over |
--dz-dropzone-border |
2px dashed #e5e7eb |
Border style |
--dz-dropzone-border-active |
2px dashed #3b82f6 |
Border when dragging over |
--dz-dropzone-border-radius |
0.8rem |
Border radius |
--dz-success-color |
#10b981 |
Success state color |
--dz-error-color |
#ef4444 |
Error state color |
For the complete list of CSS variables, see _variables.css.
The component integrates with KeenMate's theme designer --base-* variables:
web-dropzone {
--dz-accent-color: var(--base-accent-color);
--dz-text-color-1: var(--base-text-color-1);
--dz-border-color: var(--base-border-color);
}Full support for right-to-left languages:
<web-dropzone dir="rtl"></web-dropzone>
<!-- Or inherited from parent -->
<div dir="rtl">
<web-dropzone></web-dropzone>
</div>- Chrome/Edge 67+
- Firefox 63+
- Safari 10.1+
# Install dependencies
npm install
# Start dev server
npm run dev
# Build for production
npm run buildCopyright (c) 2025 Keenmate
This project is licensed under the MIT License - see the LICENSE file for details.
Created by Keenmate as part of the Pure Admin design system.