javascript erb 132 lines · 2 tabs

Stimulus controller for drag-and-drop file uploads

Jordan Lee Jan 2026
2 tabs
import { Controller } from "@hotwired/stimulus"
import { DirectUpload } from "@rails/activestorage"

export default class extends Controller {
  static targets = ["input", "preview", "status"]
  static values = {
    url: String,
    maxSize: { type: Number, default: 5242880 }, // 5MB
    accept: { type: Array, default: ["image/jpeg", "image/png", "image/gif"] }
  }

  connect() {
    this.element.addEventListener("dragover", (e) => this.dragover(e))
    this.element.addEventListener("dragleave", (e) => this.dragleave(e))
    this.element.addEventListener("drop", (e) => this.drop(e))
  }

  dragover(event) {
    event.preventDefault()
    this.element.classList.add("dragover")
  }

  dragleave(event) {
    event.preventDefault()
    this.element.classList.remove("dragover")
  }

  drop(event) {
    event.preventDefault()
    this.element.classList.remove("dragover")

    const files = Array.from(event.dataTransfer.files)
    this.uploadFiles(files)
  }

  selectFiles() {
    const files = Array.from(this.inputTarget.files)
    this.uploadFiles(files)
  }

  uploadFiles(files) {
    files.forEach(file => {
      if (!this.acceptValue.includes(file.type)) {
        this.showError(`${file.name} has unsupported type`)
        return
      }

      if (file.size > this.maxSizeValue) {
        this.showError(`${file.name} exceeds maximum size`)
        return
      }

      this.uploadFile(file)
    })
  }

  uploadFile(file) {
    const upload = new DirectUpload(file, this.urlValue)

    this.showStatus(`Uploading ${file.name}...`)

    upload.create((error, blob) => {
      if (error) {
        this.showError(`Upload failed: ${error}`)
      } else {
        this.showPreview(file, blob)
        this.showStatus("Upload complete!")
      }
    })
  }

  showPreview(file, blob) {
    if (file.type.startsWith('image/')) {
      const reader = new FileReader()
      reader.onload = (e) => {
        const img = document.createElement('img')
        img.src = e.target.result
        img.className = 'w-24 h-24 object-cover rounded'
        this.previewTarget.appendChild(img)
      }
      reader.readAsDataURL(file)
    }
  }

  showStatus(message) {
    this.statusTarget.textContent = message
    this.statusTarget.className = "text-sm text-blue-600"
  }

  showError(message) {
    this.statusTarget.textContent = message
    this.statusTarget.className = "text-sm text-red-600"
  }
}
2 files · javascript, erb Explain with highlit

Modern file uploads should support drag-and-drop in addition to traditional file inputs. I use Stimulus to handle dragover, drop, and paste events, showing upload previews and progress. The controller prevents default browser behavior for drag events and extracts files from the DataTransfer object. For images, I generate preview thumbnails using FileReader API before upload. The actual upload happens via AJAX to a dedicated endpoint that returns Turbo Stream updates. This pattern works for profile pictures, attachments, or bulk file uploads. I also handle paste events to support clipboard uploads and provide clear error messages for unsupported file types or oversized files.


Related snips

erb
<form data-controller="query-sync" data-action="change->query-sync#apply">
  <select name="status" class="rounded border p-2">
    <option value="">Any</option>
    <option value="open">Open</option>
    <option value="closed">Closed</option>
  </select>

Filter UI that syncs query params via Stimulus (no front-end router)

rails hotwire stimulus
by Henry Kim 2 tabs
javascript
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
  const success = true;

  setTimeout(() => {
    if (success) {

Promises and async/await patterns for asynchronous JavaScript

javascript promises async-await
by Alex Chang 1 tab
javascript
import { Controller } from "@hotwired/stimulus"
import Mousetrap from "mousetrap"

export default class extends Controller {
  connect() {
    // Global shortcuts

Keyboard shortcuts with Stimulus and Mousetrap

stimulus javascript ux
by Jordan Lee 2 tabs
javascript
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["form"]
  static values = { delay: { type: Number, default: 250 } }

Debounced live search with Stimulus + Turbo Streams

rails hotwire stimulus
by codesnips 4 tabs
javascript
// Get canvas and context
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

// 1. Basic shapes
// Rectangle (filled)

Canvas API for graphics and animations

canvas javascript graphics
by Alex Chang 1 tab
html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Form Validation Example</title>
  <style>

HTML forms with validation and accessibility

html forms validation
by Alex Chang 1 tab

Share this code

Here's the card — post it anywhere.

Stimulus controller for drag-and-drop file uploads — share card
Link copied