Create a stylish drag-and-drop file uploader for Salesforce Lightning Web Components
This implementation creates a custom file uploader component for LWC that:
The template defines the uploader interface with hidden file input and styled dropzone:
<!-- customFileUploader.html -->
<template>
<lightning-card title="Upload file" icon-name="doctype:link">
<div class="uploader">
<!-- Hidden file input -->
<input
id="fileInput"
class="file-input"
type="file"
accept=".csv,.gpg,.png,.pdf"
onchange={handleFileChange}
multiple
/>
<!-- Dropzone label -->
<label for="fileInput" class="dropzone">
<div class="dropzone-inner">
<svg class="icon" viewBox="0 0 24 24" aria-hidden="true">
<path d="M19 15v4H5v-4H3v4c0 1.1.9 2 2 2h14a2
2 0 0 0 2-2v-4h-2zm-6-1 3.5 3.5-1.4 1.4L13
17.8V9h-2v8.8l-2.1 1.1-1.4-1.4L11 14z"/>
</svg>
<h2 class="title">Select CSV to Upload</h2>
<p class="subtitle">Drag is decorative. Click to choose a CSV file.</p>
<p class="hint">Allowed: .csv, .gpg, .png. .pdf</p>
</div>
</label>
</div>
<!-- Helper text -->
<div class="helper">
<p>You can able to upload muliple file at a time</p>
</div>
</lightning-card>
</template>
The controller handles file selection and processing:
// customFileUploader.js
import { LightningElement } from 'lwc';
export default class CustomFileUploader extends LightningElement {
handleFileChange(event) {
// Get selected files (array-like object)
const files = event.target.files;
// Process each file
Array.from(files).forEach(file => {
this.processFile(file);
});
}
processFile(file) {
const reader = new FileReader();
reader.onload = () => {
try {
const fileData = reader.result;
console.log('File processed:', file.name);
// Here you would typically:
// 1. Validate file contents
// 2. Process the data
// 3. Upload to server or perform other actions
} catch (error) {
console.error('Error during file processing:', error?.message);
// Show error to user (e.g., using lightning/platformShowToastEvent)
}
};
reader.onerror = (error) => {
console.error('Error reading file:', error?.message);
};
// Read file based on its type
if (file.type.includes('text') || file.name.endsWith('.csv')) {
reader.readAsText(file);
} else {
reader.readAsDataURL(file);
}
}
}
CSS for the uploader interface and interactions:
/* customFileUploader.css */
.uploader {
padding: 1rem;
}
.file-input {
/* Hide the native input but keep it accessible via its label */
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0 0 0 0);
border: 0;
}
.dropzone {
display: block;
border: 2px dashed var(--lwc-colorBorder, #d8dde6);
border-radius: 1rem;
padding: 2.5rem 1.5rem;
text-align: center;
cursor: pointer;
transition: border-color 120ms ease, box-shadow 120ms ease, transform 80ms ease;
background: var(--lwc-colorBackgroundAlt, #ffffff);
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
}
.dropzone:hover {
border-color: var(--lwc-colorBorderBrand, #1b96ff);
box-shadow: 0 4px 14px rgba(0,0,0,0.08);
}
.dropzone:active {
transform: scale(0.997);
}
.dropzone-inner {
display: grid;
gap: 0.35rem;
justify-items: center;
}
.icon {
width: 40px;
height: 40px;
fill: var(--lwc-colorTextIconDefault, #54698d);
opacity: 0.9;
margin-bottom: 0.25rem;
}
.title {
font-size: 1.05rem;
font-weight: 600;
color: var(--lwc-colorTextDefault, #16325c);
margin: 0;
}
.subtitle {
font-size: 0.9rem;
color: var(--lwc-colorTextWeak, #3e5667);
margin: 0.1rem 0 0.25rem;
}
.hint {
font-size: 0.8rem;
color: var(--lwc-colorTextWeak, #6b7c93);
margin: 0;
}
.helper {
padding: 0 1rem 1.25rem;
color: var(--lwc-colorTextWeak, #3e5667);
font-size: 0.8rem;
}