Custom File Uploader in LWC

Create a stylish drag-and-drop file uploader for Salesforce Lightning Web Components

Component Overview

This implementation creates a custom file uploader component for LWC that:

Key Features

  1. Drag-and-Drop UI: Modern interface with visual feedback
  2. File Validation: Accepts specific file types (.csv, .gpg, .png, .pdf)
  3. Multiple Files: Handles multiple file uploads
  4. Accessible: Properly labeled for screen readers

LWC Component (HTML)

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>

LWC Component (JavaScript)

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 Styling

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;
}

Implementation Notes

  1. File Processing: The component currently logs files to console - you'll need to implement your specific file handling logic
  2. Error Handling: Consider adding toast messages for user feedback
  3. Server Upload: To upload to Salesforce, you'll need an Apex controller
  4. Validation: The example accepts multiple file types but doesn't validate contents

Component in Action

Enhancement Ideas