File upload
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.
<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
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
accept | accept | string | ||
multiple | multiple | boolean | false | |
maxSize | max-size | number | The largest file accepted, in bytes. | |
maxFiles | max-files | number | ||
size | sizereflects | 'sm' | 'md' | 'lg' | 'md' | |
compact | compactreflects | boolean | false | |
hideFileList | hide-file-list | boolean | false | |
autoUpload | auto-uploadreflects | boolean | false | Send each file to uploadUrl as soon as it is added. |
uploadUrl | upload-url | string | ||
uploadText | upload-text | string | 'Drop files here or' | |
browseText | browse-text | string | 'browse' | |
hint | hint | string | ||
filesread only | File[] | The files chosen so far. | ||
value | value | FormValue | '' | From FormFieldMixin. |
name | namereflects | string | From FormFieldMixin. | |
required | requiredreflects | boolean | false | From FormFieldMixin. |
disabled | disabledreflects | boolean | false | From FormFieldMixin. |
readonly | readonlyreflects | boolean | false | From FormFieldMixin. |
error | error | string | An error to show, for example one a server returned. From FormFieldMixin. | |
label | label | string | A visible label. An outside <label for> works as well. From FormFieldMixin. | |
hideRequiredIndicator | hide-required-indicator | boolean | false | From FormFieldMixin. |
touched | boolean | false | True once the user has left the control or a submit was refused. From FormFieldMixin. | |
dirty | boolean | false | True once the user has changed the value. From FormFieldMixin. | |
defaultValue | FormValue | '' | The value the control goes back to when its form is reset. From FormFieldMixin. | |
formread only | HTMLFormElement | null | From FormFieldMixin. | ||
labelsread only | NodeList | From FormFieldMixin. | ||
validityread only | ValidityState | From FormFieldMixin. | ||
validationMessageread only | string | From FormFieldMixin. | ||
willValidateread only | boolean | From FormFieldMixin. | ||
isDisabledread only | boolean | True when the element or an ancestor fieldset disables it. From FormFieldMixin. | ||
invalidread only | boolean | True when the control should look invalid. From FormFieldMixin. | ||
testId | string | Test ID for automated testing From ZenoElement. |
Methods
| Method | Description |
|---|---|
focus(options?: FocusOptions): void | Focus the component or its first focusable element From ZenoElement. |
selectFiles(): void | Opens the browser's file chooser. |
uploadFiles(): Promise<void> | Upload all pending files |
checkValidity(): boolean | Reports 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(): boolean | Checks validity and shows the result on the control. From FormFieldMixin. |
blur(): void | Blur the component or its focused element From ZenoElement. |
Events
| Event | Detail | Description |
|---|---|---|
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
| Key | What it does |
|---|---|
| Tab | Moves focus to the browse button, then to the remove button of each file in the list. |
| EnterSpace | On 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
labelattribute or by a<label for>that points at itsid. - It takes part in validation:
checkValidity(),reportValidity()andsetCustomValidity()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, fromaria-labelor 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
disabledorreadonlycontrol 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-icondoes 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,20and16, whichzeno-icondoes not have, so all of them render at the default size. - A file that is too large, of the wrong type or over
max-filesis dropped silently. The element firesfile-errorbut shows and announces nothing. - When a selection goes over
max-files, only the first extra file gets afile-errorevent. 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 noprogressbarrole 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
disabledorreadonly. - Removing a file while it uploads does not stop the upload.
auto-uploadwithoutupload-urldoes 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
acceptis set. - Several strings are English only and have no property: the default hints, “Max size”, “Remove”, the
file-errormessages 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.