# Drag and Drop: Implement File Upload UI

# 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

```html
<!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

```css
* {
    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

```javascript
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');
});
```

### Backend Example (Node
