Drag and Drop: Implement File Upload UI
Learn: Drag and Drop: Implement File Upload UI
Welcome to TopperBlog! 👋
I'm a tech content creator passionate about helping developers level up their careers and master cutting-edge technologies.
🎯 What I Write About:
• AI/ML Engineering & LLMs
• Web3 & Blockchain Development
• System Design & Architecture
• Interview Preparation (FAANG)
• Freelancing & Remote Work
• Modern Tech Stacks (Next.js, React, Rust, TypeScript)
• Performance Optimization & Best Practices
💼 Mission: Sharing practical, actionable insights that accelerate your tech career and maximize your earning potential.
📚 15+ In-Depth Guides covering everything from earning $10k/month as a freelancer to cracking FAANG interviews.
🌐 Let's connect and grow together in this amazing tech journey!
#TechBlogger #SoftwareEngineering #CareerGrowth #WebDevelopment #AIEngineering
Drag and Drop File Upload UI with HTML5
Problem
Users need an intuitive way to upload files without clicking through file dialogs. Traditional file inputs are clunky and don't provide visual feedback. We need a modern, accessible drag-and-drop interface that handles multiple files, validates them, and provides real-time feedback.
Solution
Create a drag-and-drop zone that:
- Accepts files via drag-and-drop and click
- Validates file types and sizes
- Shows visual feedback during drag operations
- Displays upload progress
- Handles errors gracefully
- Works with accessibility standards
Code
HTML Structure
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Drag & Drop File Upload</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<h1>File Upload Manager</h1>
<!-- Drop Zone -->
<div class="drop-zone" id="dropZone" role="region" aria-label="File upload area">
<svg class="upload-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="17 8 12 3 7 8"></polyline>
<line x1="12" y1="3" x2="12" y2="15"></line>
</svg>
<h2>Drop files here or click to upload</h2>
<p class="drop-zone-text">Supported: Images, PDFs, Documents (Max 10MB each)</p>
<input
type="file"
id="fileInput"
multiple
accept=".jpg,.jpeg,.png,.pdf,.doc,.docx,.txt"
hidden
aria-label="Select files to upload"
>
</div>
<!-- File List -->
<div class="file-list" id="fileList">
<h3>Uploaded Files</h3>
<ul id="fileItems" class="file-items"></ul>
</div>
<!-- Upload Button -->
<button id="uploadBtn" class="upload-button" disabled>
Upload Files
</button>
</div>
<script src="script.js"></script>
</body>
</html>
CSS Styling
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.container {
background: white;
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
padding: 40px;
max-width: 600px;
width: 100%;
}
h1 {
color: #333;
margin-bottom: 30px;
text-align: center;
font-size: 28px;
}
/* Drop Zone */
.drop-zone {
border: 3px dashed #667eea;
border-radius: 8px;
padding: 40px 20px;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
background-color: #f8f9ff;
}
.drop-zone:hover {
border-color: #764ba2;
background-color: #f0f2ff;
transform: translateY(-2px);
}
.drop-zone.drag-over {
border-color: #764ba2;
background-color: #e8ebff;
box-shadow: 0 0 20px rgba(102, 126, 234, 0.3);
transform: scale(1.02);
}
.upload-icon {
width: 60px;
height: 60px;
color: #667eea;
margin-bottom: 15px;
animation: float 3s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-10px); }
}
.drop-zone h2 {
color: #333;
font-size: 20px;
margin-bottom: 10px;
}
.drop-zone-text {
color: #666;
font-size: 14px;
}
/* File List */
.file-list {
margin-top: 30px;
display: none;
}
.file-list.active {
display: block;
}
.file-list h3 {
color: #333;
margin-bottom: 15px;
font-size: 16px;
}
.file-items {
list-style: none;
}
.file-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px;
background: #f5f5f5;
border-radius: 6px;
margin-bottom: 10px;
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.file-info {
display: flex;
align-items: center;
gap: 12px;
flex: 1;
min-width: 0;
}
.file-icon {
width: 32px;
height: 32px;
background: #667eea;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 12px;
font-weight: bold;
flex-shrink: 0;
}
.file-details {
flex: 1;
min-width: 0;
}
.file-name {
color: #333;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 14px;
}
.file-size {
color: #999;
font-size: 12px;
margin-top: 4px;
}
.file-progress {
width: 100%;
height: 4px;
background: #e0e0e0;
border-radius: 2px;
margin-top: 6px;
overflow: hidden;
}
.progress-bar {
height: 100%;
background: linear-gradient(90deg, #667eea, #764ba2);
width: 0%;
transition: width 0.3s ease;
}
.file-status {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
}
.status-icon {
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
}
.status-success {
color: #4caf50;
}
.status-error {
color: #f44336;
}
.status-loading {
color: #667eea;
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.remove-btn {
background: none;
border: none;
color: #f44336;
cursor: pointer;
font-size: 18px;
padding: 4px 8px;
border-radius: 4px;
transition: background 0.2s;
}
.remove-btn:hover {
background: rgba(244, 67, 54, 0.1);
}
/* Upload Button */
.upload-button {
width: 100%;
padding: 12px;
margin-top: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 6px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
}
.upload-button:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
}
.upload-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.upload-button.loading {
position: relative;
color: transparent;
}
.upload-button.loading::after {
content: '';
position: absolute;
width: 16px;
height: 16px;
top: 50%;
left: 50%;
margin-left: -8px;
margin-top: -8px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: white;
animation: spin 0.8s linear infinite;
}
/* Responsive */
@media (max-width: 600px) {
.container {
padding: 20px;
}
h1 {
font-size: 24px;
}
.drop-zone {
padding: 30px 15px;
}
.upload-icon {
width: 48px;
height: 48px;
}
}
JavaScript Implementation
class FileUploadManager {
constructor(dropZoneId, fileInputId, fileListId, uploadBtnId) {
this.dropZone = document.getElementById(dropZoneId);
this.fileInput = document.getElementById(fileInputId);
this.fileList = document.getElementById(fileListId);
this.uploadBtn = document.getElementById(uploadBtnId);
this.fileItems = document.getElementById('fileItems');
this.files = new Map();
this.config = {
maxSize: 10 * 1024 * 1024, // 10MB
allowedTypes: ['image/jpeg', 'image/png', 'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'text/plain'],
allowedExtensions: ['.jpg', '.jpeg', '.png', '.pdf', '.doc', '.docx', '.txt']
};
this.init();
}
init() {
this.setupEventListeners();
}
setupEventListeners() {
// Click to upload
this.dropZone.addEventListener('click', () => this.fileInput.click());
this.fileInput.addEventListener('change', (e) => this.handleFiles(e.target.files));
// Drag and drop events
this.dropZone.addEventListener('dragover', (e) => this.handleDragOver(e));
this.dropZone.addEventListener('dragleave', (e) => this.handleDragLeave(e));
this.dropZone.addEventListener('drop', (e) => this.handleDrop(e));
// Upload button
this.uploadBtn.addEventListener('click', () => this.uploadFiles());
// Prevent default drag behavior on document
document.addEventListener('dragover', (e) => e.preventDefault());
document.addEventListener('drop', (e) => e.preventDefault());
}
handleDragOver(e) {
e.preventDefault();
e.stopPropagation();
this.dropZone.classList.add('drag-over');
}
handleDragLeave(e) {
e.preventDefault();
e.stopPropagation();
// Only remove class if leaving the drop zone entirely
if (e.target === this.dropZone) {
this.dropZone.classList.remove('drag-over');
}
}
handleDrop(e) {
e.preventDefault();
e.stopPropagation();
this.dropZone.classList.remove('drag-over');
const droppedFiles = e.dataTransfer.files;
this.handleFiles(droppedFiles);
}
handleFiles(fileList) {
Array.from(fileList).forEach(file => {
const validation = this.validateFile(file);
if (validation.valid) {
const fileId = this.generateFileId();
this.files.set(fileId, {
file,
id: fileId,
status: 'pending',
progress: 0
});
this.renderFile(fileId);
} else {
this.showError(file.name, validation.error);
}
});
this.updateUI();
}
validateFile(file) {
// Check file size
if (file.size > this.config.maxSize) {
return {
valid: false,
error: `File too large. Max size: 10MB`
};
}
// Check file type
const extension = '.' + file.name.split('.').pop().toLowerCase();
if (!this.config.allowedExtensions.includes(extension)) {
return {
valid: false,
error: `File type not allowed: ${extension}`
};
}
return { valid: true };
}
generateFileId() {
return `file_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
renderFile(fileId) {
const fileData = this.files.get(fileId);
const { file } = fileData;
const li = document.createElement('li');
li.className = 'file-item';
li.id = fileId;
const extension = file.name.split('.').pop().toUpperCase();
const sizeInMB = (file.size / (1024 * 1024)).toFixed(2);
li.innerHTML = `
<div class="file-info">
<div class="file-icon">${extension}</div>
<div class="file-details">
<div class="file-name" title="${file.name}">${file.name}</div>
<div class="file-size">${sizeInMB} MB</div>
<div class="file-progress">
<div class="progress-bar" style="width: 0%"></div>
</div>
</div>
</div>
<div class="file-status">
<span class="status-icon">
<span class="status-loading">⏳</span>
</span>
</div>
<button class="remove-btn" aria-label="Remove file">✕</button>
`;
const removeBtn = li.querySelector('.remove-btn');
removeBtn.addEventListener('click', () => this.removeFile(fileId));
this.fileItems.appendChild(li);
}
removeFile(fileId) {
this.files.delete(fileId);
const element = document.getElementById(fileId);
if (element) {
element.remove();
}
this.updateUI();
}
updateUI() {
const hasFiles = this.files.size > 0;
this.fileList.classList.toggle('active', hasFiles);
this.uploadBtn.disabled = !hasFiles;
}
async uploadFiles() {
if (this.files.size === 0) return;
this.uploadBtn.classList.add('loading');
this.uploadBtn.disabled = true;
for (const [fileId, fileData] of this.files) {
await this.uploadFile(fileId, fileData);
}
this.uploadBtn.classList.remove('loading');
this.uploadBtn.disabled = true;
}
uploadFile(fileId, fileData) {
return new Promise((resolve) => {
const { file } = fileData;
const formData = new FormData();
formData.append('file', file);
const xhr = new XMLHttpRequest();
// Progress tracking
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
const percentComplete = (e.loaded / e.total) * 100;
this.updateFileProgress(fileId, percentComplete);
}
});
// Completion
xhr.addEventListener('load', () => {
if (xhr.status === 200) {
this.updateFileStatus(fileId, 'success', '✓');
fileData.status = 'success';
} else {
this.updateFileStatus(fileId, 'error', '✗');
fileData.status = 'error';
}
resolve();
});
// Error
xhr.addEventListener('error', () => {
this.updateFileStatus(fileId, 'error', '✗');
fileData.status = 'error';
resolve();
});
// Send request
xhr.open('POST', '/api/upload');
xhr.send(formData);
});
}
updateFileProgress(fileId, progress) {
const element = document.getElementById(fileId);
if (element) {
const progressBar = element.querySelector('.progress-bar');
progressBar.style.width = `${progress}%`;
this.files.get(fileId).progress = progress;
}
}
updateFileStatus(fileId, status, icon) {
const element = document.getElementById(fileId);
if (element) {
const statusIcon = element.querySelector('.status-icon');
statusIcon.innerHTML = `<span class="status-${status}">${icon}</span>`;
statusIcon.className = `status-icon status-${status}`;
}
}
showError(fileName, error) {
const notification = document.createElement('div');
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: #f44336;
color: white;
padding: 16px 24px;
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 1000;
animation: slideIn 0.3s ease;
`;
notification.textContent = `${fileName}: ${error}`;
document.body.appendChild(notification);
setTimeout(() => notification.remove(), 4000);
}
}
// Initialize
document.addEventListener('DOMContentLoaded', () => {
new FileUploadManager('dropZone', 'fileInput', 'fileList', 'uploadBtn');
});