Direct Upload Progress

Show a progress bar for each file as it uploads via Rails ActiveStorage direct uploads.

Usage

Copy direct_upload_progress_controller.js to app/javascript/controllers/ and register it:

// app/javascript/controllers/index.js
import DirectUploadProgressController from "./direct_upload_progress_controller";
application.register("direct-upload-progress", DirectUploadProgressController);

activestorage’s direct upload JS already dispatches direct-upload:* custom events on the file input — this controller just listens for them and renders the UI. No server-side changes are needed beyond the usual data-direct-upload-url setup.

HTML

<div data-controller="direct-upload-progress">
  <label for="attachments">Attachments</label>
  <input
    id="attachments"
    type="file"
    multiple
    name="post[attachments][]"
    data-direct-upload-url="/rails/active_storage/direct_uploads"
    data-action="
      direct-upload:initialize->direct-upload-progress#add
      direct-upload:progress->direct-upload-progress#progress
      direct-upload:error->direct-upload-progress#error
      direct-upload:end->direct-upload-progress#end
    "
  />

  <ul data-direct-upload-progress-target="list"></ul>
  <p data-direct-upload-progress-target="status"></p>
</div>

Each selected file gets its own <li> containing the file name and a native <progress> element, added when ActiveStorage dispatches direct-upload:initialize and updated as direct-upload:progress events arrive.

API

Targets

TargetRequiredDescription
listYesContainer that receives one <li> per file being uploaded.
statusNoReceives a short text message on upload start/success/failure. Gets aria-live="polite" auto-applied if not already set.

Actions

ActionWire toDescription
adddirect-upload:initializeAdds a progress item for the file.
progressdirect-upload:progressUpdates the item’s <progress> value (event.detail.progress is 0–100).
errordirect-upload:errorMarks the item as errored and announces the failure message.
enddirect-upload:endMarks a non-errored item as done and sets its progress to 100.

All four events are dispatched by ActiveStorage’s own direct upload JS on the file input element — wire them all via data-action on the input, as shown above.

Item state

Each <li> gets data-direct-upload-progress-state, one of uploading, done, or error. Use it to style the row (e.g. a red border on error). The filename <span> inside it carries data-direct-upload-progress-role="name" so you can target it in CSS.

Accessibility

  • Each file’s <progress> element has an aria-label of the file name, so screen reader users hear which file a given progress bar belongs to.
  • The optional status target gets aria-live="polite" applied automatically (unless already set) and is updated only on upload start, success, and failure — not on every percentage tick, to avoid spamming screen reader users.