Skip to main content

File upload

<zeno-file-upload>FormsForm controlSource

A drop zone and file list. In a form it submits the chosen files under its name.

import '@usezeno/tokens/css';
import '@usezeno/core/web-components';

Examples​

One file​

Without multiple the control keeps one file. Choosing another file replaces it.

<zeno-file-upload label="Passport scan" name="passport"></zeno-file-upload>

Several files with limits​

max-size is in bytes. A file that is refused only fires file-error, so the page has to show the message.

No file was refused.

<zeno-file-upload
label="Receipts"
name="receipts"
multiple
accept="image/*,.pdf"
max-size="1048576"
max-files="3"
></zeno-file-upload>
<p data-result>No file was refused.</p>
const upload = document.querySelector('zeno-file-upload');
upload.addEventListener('file-error', event => {
document.querySelector('[data-result]').textContent =
event.detail.file.name + ': ' + event.detail.error;
});

Compact, with your own text​

upload-text, browse-text and hint replace the default English text.

<zeno-file-upload
label="Profile photo"
compact
size="sm"
accept="image/*"
upload-text="Drag a photo here or"
browse-text="choose a file"
hint="PNG or JPEG"
></zeno-file-upload>

Disabled and invalid​

<zeno-file-upload label="Signed contract" disabled></zeno-file-upload>
<zeno-file-upload label="Proof of address" error="Upload a file that is less than 3 months old."></zeno-file-upload>

In a form​

The files are sent under name. A required control blocks the submit until a file is chosen.

Send application

Nothing submitted yet.

<form>
<zeno-file-upload label="Cover letter" name="letter" accept=".pdf" required></zeno-file-upload>
<zeno-button type="submit">Send application</zeno-button>
<p data-result>Nothing submitted yet.</p>
</form>
const form = document.querySelector('form');
form.addEventListener('submit', event => {
event.preventDefault();
const names = new FormData(form).getAll('letter').map(file => file.name);
document.querySelector('[data-result]').textContent = 'Submitted: ' + names.join(', ');
});

Attributes and properties​

PropertyAttributeTypeDefaultDescription
acceptacceptstring
multiplemultiplebooleanfalse
maxSizemax-sizenumberThe largest file accepted, in bytes.
maxFilesmax-filesnumber
sizesizereflects'sm' | 'md' | 'lg''md'
compactcompactreflectsbooleanfalse
hideFileListhide-file-listbooleanfalse
autoUploadauto-uploadreflectsbooleanfalseSend each file to uploadUrl as soon as it is added.
uploadUrlupload-urlstring
uploadTextupload-textstring'Drop files here or'
browseTextbrowse-textstring'browse'
hinthintstring
filesread onlyFile[]The files chosen so far.
valuevalueFormValue'' From FormFieldMixin.
namenamereflectsstring From FormFieldMixin.
requiredrequiredreflectsbooleanfalse From FormFieldMixin.
disableddisabledreflectsbooleanfalse From FormFieldMixin.
readonlyreadonlyreflectsbooleanfalse From FormFieldMixin.
errorerrorstringAn error to show, for example one a server returned. From FormFieldMixin.
labellabelstringA visible label. An outside <label for> works as well. From FormFieldMixin.
hideRequiredIndicatorhide-required-indicatorbooleanfalse From FormFieldMixin.
touchedbooleanfalseTrue once the user has left the control or a submit was refused. From FormFieldMixin.
dirtybooleanfalseTrue once the user has changed the value. From FormFieldMixin.
defaultValueFormValue''The value the control goes back to when its form is reset. From FormFieldMixin.
formread onlyHTMLFormElement | null From FormFieldMixin.
labelsread onlyNodeList From FormFieldMixin.
validityread onlyValidityState From FormFieldMixin.
validationMessageread onlystring From FormFieldMixin.
willValidateread onlyboolean From FormFieldMixin.
isDisabledread onlybooleanTrue when the element or an ancestor fieldset disables it. From FormFieldMixin.
invalidread onlybooleanTrue when the control should look invalid. From FormFieldMixin.
testIdstringTest ID for automated testing From ZenoElement.

Methods​

MethodDescription
focus(options?: FocusOptions): voidFocus the component or its first focusable element From ZenoElement.
selectFiles(): voidOpens the browser's file chooser.
uploadFiles(): Promise<void>Upload all pending files
checkValidity(): booleanReports whether the value is valid without showing anything. Use reportValidity() or validate() to show the result as well. From FormFieldMixin.
reportValidity(): boolean From FormFieldMixin.
setCustomValidity(message: string): void From FormFieldMixin.
validate(): booleanChecks validity and shows the result on the control. From FormFieldMixin.
blur(): voidBlur the component or its focused element From ZenoElement.

Events​

EventDetailDescription
input{ files: File[] }The list of files changed.
change{ files: File[] }The list of files changed.
files-added{ files: UploadedFile[] }Files were added.
file-removed{ file: UploadedFile }A file was removed.
file-error{ file: File; error: string }A file was refused.
upload-success{ file: UploadedFile }An upload finished.
upload-error{ file: UploadedFile; error: unknown }An upload failed.

Slots​

This element has no slots.

CSS parts​

Part
base
browse
dropzone
error
file-error
file-icon
file-info
file-item
file-name
file-progress
file-progress-bar
file-remove
file-size
files
hint
icon
label
text

CSS custom properties​

This element documents no custom properties of its own. Style it through its parts and through the tokens it reads.

The element's own source reads no tokens.

Types​

type FormValue = string | number | boolean | Date | null | undefined;

interface UploadedFile {
id: string;
file: File;
progress: number;
status: 'pending' | 'uploading' | 'success' | 'error';
error?: string;
}

Accessibility​

Keyboard​

KeyWhat it does
TabMoves focus to the browse button, then to the remove button of each file in the list.
EnterSpaceOn the browse button, opens the file chooser of the browser. On a remove button, removes that file.

Forms​

  • With a name, the element adds its value to the data of the form it is in, and is left out while it is disabled.
  • It is labelled by its label attribute or by a <label for> that points at its id.
  • It takes part in validation: checkValidity(), reportValidity() and setCustomValidity() work, and an invalid element blocks the submit and matches :invalid.
  • It follows a <fieldset disabled> around it and goes back to its first value when the form is reset.
  • The messages it writes itself, such as “Please fill out this field.”, are in English only.

Notes​

  • The control is a native <button> in the shadow root. The area around it only adds a larger target for the pointer and for dropped files, and is not focusable.
  • The button is named by the label and the visible text together, for example “Passport scan: Drop files here or browse”. The label comes from label, from aria-label or from an outside <label for>.
  • The hint is linked to the button with aria-describedby. When an error shows, its text is linked the same way.
  • Each file in the list has a native remove button named “Remove” and the file name. After a file is removed, focus moves to the browse button.
  • A disabled or readonly control disables the browse button and ignores dropped files.

Tests and known gaps​

  • Tested in a real browser by file-upload.test.ts.
  • Checked with axe-core in the light and the dark theme.
  • Has no Storybook story.
41 of the 65 properties, methods, events, slots and parts above have no description in the source
  • the property accept
  • the property multiple
  • the property maxFiles
  • the property size
  • the property compact
  • the property hideFileList
  • the property uploadUrl
  • the property uploadText
  • the property browseText
  • the property hint
  • the property value
  • the property name
  • the property required
  • the property disabled
  • the property readonly
  • the property hideRequiredIndicator
  • the property form
  • the property labels
  • the property validity
  • the property validationMessage
  • the property willValidate
  • the method reportValidity()
  • the method setCustomValidity()
  • the part base
  • the part browse
  • the part dropzone
  • the part error
  • the part file-error
  • the part file-icon
  • the part file-info
  • the part file-item
  • the part file-name
  • the part file-progress
  • the part file-progress-bar
  • the part file-remove
  • the part file-size
  • the part files
  • the part hint
  • the part icon
  • the part label
  • the part text

Known gaps:

  • The drop area icon, the file type icons and the status icons use icon names that zeno-icon does not have. They show nothing unless the page registers those names. Only the remove icon is built in.
  • The element gives its icons the sizes 48, 20 and 16, which zeno-icon does not have, so all of them render at the default size.
  • A file that is too large, of the wrong type or over max-files is dropped silently. The element fires file-error but shows and announces nothing.
  • When a selection goes over max-files, only the first extra file gets a file-error event. The rest are dropped without one.
  • Nothing announces that a file was added or removed. The file list has no list role and no live region.
  • The upload progress bar is a plain div. It has no progressbar role and no value for assistive technology.
  • A finished upload is marked only by a status icon that has no label. The success colour in the stylesheet is never applied. Only a failed upload gets text.
  • The remove buttons still work when the control is disabled or readonly.
  • Removing a file while it uploads does not stop the upload.
  • auto-upload without upload-url does nothing and gives no warning.
  • For a single-file control the default hint is “Single file only”. It does not show the accepted types, even when accept is set.
  • Several strings are English only and have no property: the default hints, “Max size”, “Remove”, the file-error messages and the required message “Please select a file.”
  • The wrapper has role="group" but no accessible name.
  • The error text is not a live region. It is only read when focus is on the browse button.