Skip to content

Upload ​

Click or drag to select files; displays the file list and upload progress. Supports a real upload channel (action XHR / custom-request escape hatch), before-upload validation & transform, failure retry and cancel.

Basic Usage ​

Basic (select files)

Multiple & Limit ​

Multiple + max

max limits the maximum number of selectable files; accept filters file types. Files exceeding max are rejected and an oas-exceed-limit event is emitted.

Auto Upload ​

Auto upload (simulated progress)

With auto-upload, adding a file automatically starts uploading. When neither action nor custom-request is set, progress is simulated (built-in timer fallback, no network request); the progress bar reuses oas-progress.

Drag & Drop ​

Drag & drop upload

Drag files directly into the dashed area and release to add them (multiple supported); the area highlights while dragging. When disabled, the drag shows a not-allowed cursor and drops are ignored.

Picture Wall ​

list-type controls the list style: list (default, text rows), picture (rows with small thumbnails), picture-card (card thumbnail wall).

picture-card ​

Picture wall (picture-card)

Image files render thumbnails (URL.createObjectURL); non-image files show a file icon and name. Click a thumbnail to open the preview overlay (close with Esc or by clicking the mask); remove via the top-right ×, and hover shows preview/remove actions. A circular avatar card can be achieved with the CSS variable --oas-upload-card-radius: 50%.

On touch screens (pointer: coarse) there is no hover to rely on: the top-right remove × stays visible, the action overlay (preview/retry/cancel) stays visible on error/uploading cards, and the remove × / action buttons get a 44px hit target.

Remove, preview & max rejection ​

Picture wall · remove/preview/exceed

A 4th file is rejected by max="3" and triggers oas-exceed-limit with a warning; clicking a thumbnail triggers oas-preview.

picture rows with thumbnails ​

Rows with thumbnails (picture)

In list / picture modes, clicking the file name also opens the preview overlay (the name is a focusable button).

At the max ​

Reached max (pre-filled with 3)

Pre-filled to max="3"; further selections are rejected via oas-exceed-limit.

Disabled ​

Disabled

Real Upload Channel (action) ​

With action set, the built-in XHR channel is used: name (form file field, default file), data (extra form fields JSON), headers (request headers JSON), method (default POST) and with-credentials are passed through; progress/success/failure come from real XHR events.

action channel (with failure/retry demo)

The docs site has no real upload endpoint — /api/upload returns 404, which conveniently demonstrates the failure state: the row turns red with a retry button (⟳); clicking retry re-sends the request; while uploading you can click × to cancel (emits oas-cancel). Success emits oas-success (detail: { file, response }, where response is parsed JSON or raw text); failure emits oas-error (detail: { file, response, status }).

Custom Request Channel (custom-request) ​

Chunked uploads, object-storage direct upload, WebSocket and other non-standard channels are taken over by the custom-request function property (the component does not care about request strategy):

custom-request takes over the request

js
el.customRequest = ({ file, name, action, onProgress, onSuccess, onError }) => {
  // send the request yourself; callback pacing is controlled by the host
  onProgress({ percent: 60 })
  onSuccess({ url: 'https://example.com/f.png' }) // or onError({ status: 500 })
  return { abort: () => {/* cancel */} } // optional: a cancel button appears while uploading
}

Manual Upload ​

Without auto-upload the component is in manual mode: after selecting files, call submit() (an alias of startUpload()) to upload them together.

Manual upload (submit together)

Start upload

Before Upload (validation & transform) ​

The before-upload function property: return false to reject a file; return a new File to transform (rename/compress); async Promise is supported.

before-upload validation & transform

Image Crop (crop) ​

crop enables pre-upload image cropping. Activation conditions: list-type is picture / picture-card and accept includes image types — selecting / dropping / pasting an image file then opens a built-in lightweight crop dialog (self-drawn overlay, zero dependencies) instead of queueing directly:

  • Move: drag the stage's empty area to pan the image; drag the crop frame to move it (arrow keys nudge when focused, Shift to speed up)
  • Resize the frame: drag the bottom-right handle; set a fixed aspect ratio with crop-aspect (e.g. 1:1, 16:9; invalid/missing = free ratio)
  • Zoom the image: mouse wheel on the stage, or the zoom in/out buttons (100% = cover baseline, up to 800%)
  • Confirm: an offscreen canvas exports the cropped region at the source's natural resolution; the result file is queued in place of the original and oas-crop is dispatched (detail: { file, blob })
  • Cancel / Esc: closes without queueing; with multi-select, files are cropped one by one (cancel skips the current one and continues), non-image files queue directly

crop before upload (picture-card + 1:1)

Combined with before-upload, the hook runs first (validation/transform) and the transformed result enters cropping; queuing still respects max / max-size / replace (a cropped result may be smaller than the original — size limits are judged at queue time).

File Size Limit (max-size) ​

max-size accepts bytes or a unit form (512KB / 2MB / 1GB); oversized files are rejected with oas-exceed-limit (detail.type === 'size', including the maxSize in bytes).

max-size (100KB)

Replace on Exceed ​

With replace, exceeding the count limit no longer rejects: the oldest file is removed to make room (for max=1 / single-select this is "new replaces old" — no more "delete first, then select" for avatar-like scenarios).

replace (max=1, selecting a new file replaces the old)

Directory & Paste Upload ​

directory enables directory upload (sets webkitdirectory on the input; the list shows relative paths). paste enables paste upload (Ctrl+V clipboard files directly on the component — great for screenshots; off by default).

Directory upload (directory)

Paste upload (paste)

Trigger, Tip & Item Customization ​

  • slot="trigger": replaces the drop zone content (click/drag semantics stay on the zone container — the zone itself is the focusable role="button" control, so do not nest interactive elements (buttons/links) inside the trigger content (nested-interactive is an axe violation); presentational content only)
  • tip attribute or template[slot="tip"]: secondary hint text below the zone (attribute wins)
  • template[slot="item"]: cloned into every list / picture row; [data-item-name] / [data-item-size] bind the file name and size automatically

trigger / tip slots

item slot (custom list rows)

Echo Back & Hiding the List ​

  • The files / defaultFiles properties accept { name, url, size? } echo records (status done, preview uses the remote url) — the initial-value semantics for edit scenarios
  • show-file-list="false" hides the built-in list, leaving only the trigger area (premise for host-drawn previews)

Echo back (defaultFiles)

Avatar (show-file-list=false + custom trigger + before-upload validation)

Events ​

Change events

Listen to oas-change / oas-remove / oas-upload / oas-progress:

  • oas-upload: progress, detail: { file, percent, status } (status is only uploading / done; failures are not reported here but via oas-error)
  • oas-progress: upload progress callback (PRD P2), detail: { file, percent } — internal progress already drives the UI; this event adds the outbound channel (emitted by real / mock / custom-request channels alike, with 100 on success)
  • oas-success / oas-error: upload success/failure (real channel), detail: { file, response, status? }
  • oas-retry / oas-cancel: retry started / upload cancelled, detail: { file }
  • oas-exceed-limit: rejection, detail: { files, max, total } (count) or { files, type: 'size', maxSize, total } (size)
  • oas-remove: file removed, detail: { file, index, replaced? } (replaced: true means removed by the replace semantics)
  • oas-crop: crop confirmed, detail: { file, blob } (file is the original file, blob the offscreen canvas crop result)

Methods: submit() (manual upload), startUpload() (equivalent), abort(file?) (cancel one/all in-flight uploads).

oas-progress feedback

Third-party Composition Scenarios ​

These scenarios are not part of the component API; compose them via existing channels:

js
// Large-file chunking / object-storage direct upload: take over via custom-request
// (chunking strategy is a business decision)
el.customRequest = async ({ file, onProgress, onSuccess, onError }) => {
  const chunks = sliceFile(file, 5 * 1024 * 1024) // chunk it yourself
  for (let i = 0; i < chunks.length; i++) {
    await uploadChunk(chunks[i], i)
    onProgress({ percent: ((i + 1) / chunks.length) * 100 })
  }
  onSuccess(await assembleChunks(file))
}
  • Avatar cropping: covered by the built-in crop channel (see "Image Crop"); to use a third-party cropper instead, return the cropped new File from before-upload
  • List drag sorting: customize rows via the item slot + reorder files with a third-party dnd library
  • Circular avatar card: CSS variable --oas-upload-card-radius: 50% (no separate enum)

required ​

required (native validation chain)

required drives the native validation chain (form-associated): when unfilled, checkValidity() returns false (valueMissing) and native form submission is blocked; it recovers to :valid once filled.

The current form value is available through the public value property for read/write: el.value returns the submittable File array ({name,url} echo records carry no file body and are not included; an empty array when there are no files); el.value = [file] replaces the whole file list (same controlled channel as the files property, no events dispatched).

API ​

oas-upload ​

Attributes ​

AttributeDescriptionTypeDefault
acceptAccepted file typesstring—
actionUpload endpoint URL (real XHR channel; without it and customRequest, progress is simulated)string—
auto-uploadAuto-simulate upload after addingboolean—
cropPre-upload image cropping (picture/picture-card + image accept): picked images open the built-in crop dialog first; the canvas crop result is queued on confirmboolean—
crop-aspectFixed crop aspect ratio (e.g. 1:1, 16:9); invalid/missing = free ratiostring—
dataExtra form fields (JSON string, submitted with the file)——
directoryDirectory upload (recursive folder intake)boolean—
disabledDisabledboolean—
headersExtra request headers (JSON string)——
list-typeList style: list (default) / picture (rows with thumbnails) / picture-card (card thumbnail wall)stringlist
maxMaximum number of filesstring0
max-sizePer-file size limit (bytes or 2KB/1MB/1GB; over limit fires oas-exceed size form)string—
methodHTTP method (default POST)stringPOST
multipleMultiple selectionboolean—
nameForm field name for the file (default file)string—
pastePaste upload (off by default; clipboard files enter the list when on)boolean—
replaceReplace semantics at max-count=1 (new file replaces the old instead of being rejected)boolean—
requiredRequired marker (drives the native valueMissing validation chain; not passed through to the inner control)boolean—
show-file-listFile list visibility ("false" hides it, keeping only the drop zone)stringtrue
tipDrop-zone tip text (same as template[slot="tip"] for rich content)string—
with-credentialsCross-origin credentialsboolean—

Property (JS property; see each row for attribute reflection) ​

PropertyDescriptionTypeDefault
filesControlled file list (property channel; assigning re-renders)Array<File | UploadEchoFile>[]
valueCurrent value: the submittable File[] (file-body entries only; echo records without a body are excluded)File[] | null[]

Events ​

EventDescription
oas-cancelUpload cancelled, detail: { file } (cancel button on the item)
oas-changeFile list change, detail: { files }
oas-cropCrop confirmed, detail: { file, blob } (file = original file, blob = offscreen canvas result)
oas-errorUpload failed, detail: { file, response?, status? } (retry button appears)
oas-exceed[Compat alias] Same as oas-exceed-limit; will be removed later, detail: { files: rejected, max, total: next.length } | { files: sizeRejected, type: 'size', maxSize, total: next.length }
oas-exceed-limitAdding files rejected due to limit (canonical, aligned with checkbox-group/select/toggle-group), detail: { files, max, total }; emitted for both count and size rejections (size rejections also carry type: 'size' and maxSize)
oas-previewPreview overlay opened, detail: { file, url }
oas-progressUpload progress { file, percent }, emitted across all channels with 100 on success, detail: { file, percent: cur.percent } | { file, percent: 100 } | { file: f, percent: st.percent }
oas-removeFile removed, detail: { file, index }
oas-retryRetry clicked, detail: { file } (re-upload after failure)
oas-successUpload succeeded, detail: { file, response }
oas-uploadUpload progress, detail: { file, percent, status }

Slots ​

NameDescription
template[slot="item"]Custom file row/card ([data-item-name]/[data-item-size] bindings)
template[slot="tip"]Drop-zone tip rich content (tip attribute wins)
template[slot="trigger"]Self-drawn trigger-area template (native slot does not render template elements — cloned by the component; clicking the zone still opens the file picker)
triggerReplace the drop-zone content (zone semantics kept)

CSS Variables ​

CSS VariableDefault
--oas-upload-card-radiusvar(--oas-radius-md)

⚠️ Three limitations of template[slot="trigger"]: ① the template content is cloned into the shadow DOM — host-page stylesheets do not apply to cloned content (use inline styles / oas components / CSS variables instead); ② the clone is a static snapshot taken at component update() — template changes after connect do not auto-refresh (write the template before mounting the component, or nudge any attribute afterwards to trigger a re-sync); ③ cloneNode does not carry event listeners — @click / addEventListener bound on the template nodes are lost in the clone; use component events (oas-change, etc.) or delegate on the cloned zone container for interactivity.

Keyboard: Enter / Space on the drop zone opens the file picker; remove buttons are focusable; the preview overlay closes with Esc.

Released under the MIT OR Apache-2.0 License.