File Upload

Drag-and-drop and click-to-browse file upload zone.

Dropzone — Multiple Files

Click the dropzone or drag files onto it. Images get a thumbnail preview; other files fall back to a generic icon. Each file shows a simulated upload progress bar and can be removed individually.

<div class="sc-fu" x-data="fileUpload({ multiple: true })">
  <label class="sc-fu-dropzone" @click="$refs.fileInput.click()"
         @drop.prevent="onDrop($event)" @dragover.prevent="isDragging = true">
    <input type="file" multiple x-ref="fileInput" @change="onBrowse($event)" />
  </label>
  <template x-for="entry in files" :key="entry.id">
    <div>...<button @click="remove(entry)">✕</button></div>
  </template>
</div>

Single File — Image Only, 2MB Limit

Restricted to image/* and 2MB max. Selecting a second file replaces the first; a rejected file shows the reason rather than failing silently.

States

Empty, failed-upload, and disabled states.

Empty (no files yet)

Failed Upload

contract-final.pdf

Upload failed — connection lost

Disabled

Progress here is simulated with setTimeout in js/ui/components/form.js's _simulateProgress() — replace that with real upload-progress events (XHR's progress event, or a fetch-based upload library) when wiring this into a project. Everything else (validation, thumbnails, drag state, removal) needs no changes.