A visually hidden native file input is the control: it takes focus, opens the picker with Enter or Space and carries the Field label and errors. Clicking anywhere on the drop surface also opens the picker, and dropping files on it adds them. Accepted files submit with the form under name. The component never transfers a file unless you give it an upload adapter.
Its name comes from the Field label, an aria-label on the host, or the title; its description includes the hint. Added and rejected files are announced once per batch through a polite live region; item status text sits in each Attachment’s status region, and progress values are not announced. Remove and Retry Buttons are named with the file name. The item list uses list semantics and is marked busy while files are checked or uploaded.
Drop surface
The surface generates its title and a hint from accept and the size limit. The title and description slots replace them with your own wording, and media replaces the upload mark. show-trigger adds a Browse Button; it duplicates the surface’s own action and is not a separate tab stop. size="sm" makes a compact surface for sidebars and dialogs.
Custom text and compact size
Default density
Compact density
Restart from the initial state
Show the copyable source
Restore from a backupA .sql, .sql.gz or .dump file from pg_dump, up to 10 GB
Restore from a backupA .sql, .sql.gz or .dump file from pg_dump, up to 10 GB
<div style="display:grid;gap:var(--tp-space-6);inline-size:100%;max-inline-size:30rem;margin-inline:auto">
<tp-file-upload name="backup" accept=".sql,.sql.gz,.dump" max-file-size="10GB">
<span slot="title">Restore from a backup</span>
<span slot="description">A .sql, .sql.gz or .dump file from pg_dump, up to 10 GB</span>
</tp-file-upload>
<tp-file-upload name="logo" size="sm" accept="image/png,image/svg+xml" max-file-size="2MB" show-trigger></tp-file-upload>
</div>
Without multiple a new file replaces the current one, which suits a single logo or backup.
Limits and validation
accept takes extensions, media types and wildcards such as image/*; it filters the picker and checks every dropped file too. max-file-size, min-file-size and max-total-size accept bytes or size text such as 25MB. With multiple, max-files caps the count: a batch fills the remaining slots and rejects the rest. hint-treatment="badges" lists the rules as Badges instead of a sentence.
Rejected files
Default density
Compact density
Restart from the initial state
Show the copyable source
Rejected files stay in the list as error items with the reason, and they block form submission until they are removed. Set keep-rejected="false" to only announce them and report them through tp-file-reject.
For rules about a file’s contents, assign a fileValidator function. It receives each file in turn and returns nothing to accept it, a message or { code, message } to reject it, or { meta, description } to attach metadata such as a row count.
Custom validator
Default density
Compact density
Restart from the initial state
Show the copyable source
Import contactsCSV with name, email and phone columns
Import contactsCSV with name, email and phone columns
A thrown error or rejected promise rejects the file; it is never accepted by accident.
Each batch is checked in order. Folders are rejected unless directory is set. Each file is then checked for type, size and duplicates, collecting every error that applies. Your fileValidator runs next, one file at a time. Count and total-size limits come last. Built-in error codes are file-invalid-type, file-too-large, file-too-small, too-many-files, total-too-large, file-exists, folder-not-allowed, file-invalid and validation-failed. Upload failures use upload-failed.
Rejected files never enter the value or the form data.
Text errors use file-invalid. Removing a file while it is checked excludes it; clearing, reset and disconnection abort the signal and ignore late results.
Layouts
Files are listed as Attachment rows by default. item-size="xs" wraps them as chips, which keeps a composer or a comment box short; clearable adds a Clear all action to the summary.
Chips
Default density
Compact density
Restart from the initial state
Show the copyable source
Assigning value sets files without validation or an event, which suits restoring a draft. Use addFiles() when the files should be checked.
layout="grid" shows image previews as tiles, with the drop surface as a final Add tile that hides while max-files is reached. paste="control" also accepts an image pasted while the grid has focus or the pointer is over it.
Image grid
Default density
Compact density
Restart from the initial state
Show the copyable source
Assign an uploader to transfer files. It is called once per accepted file with an AbortSignal and an onProgress(loaded, total) callback, and its result is stored as the item’s response. Files queue and start in order with upload-concurrency in flight; a rejected promise shows the error with a Retry Button, and removing a file aborts its upload. Set auto-upload="false" to start them yourself with upload().
Upload adapter
Default density
Compact density
Restart from the initial state
Show the copyable source
Files upload as soon as they are added
Files upload as soon as they are added
<tp-file-upload id="file-upload-adapter" name="assets" multiple upload-concurrency="2" style="inline-size:100%;max-inline-size:30rem;margin-inline:auto">
<span slot="description">Files upload as soon as they are added</span>
</tp-file-upload>
<script type="module">
await customElements.whenDefined('tp-file-upload');
const upload = document.querySelector('#file-upload-adapter');
const attempts = new Map();
// Stands in for fetch or XMLHttpRequest: reports progress, and fails a file named "*-fail*" once.
upload.uploader = (item, { signal, onProgress }) => new Promise((resolve, reject) => {
const total = item.file.size || 1;
let loaded = 0;
const timer = setInterval(() => {
loaded = Math.min(total, loaded + total / 12);
onProgress(loaded, total);
if (loaded < total) return;
clearInterval(timer);
const attempt = (attempts.get(item.id) ?? 0) + 1;
attempts.set(item.id, attempt);
if (item.file.name.includes('-fail') && attempt === 1) reject(new Error('Server returned 503. Try again.'));
else resolve({ url: `/files/${encodeURIComponent(item.file.name)}` });
}, 250);
signal.addEventListener('abort', () => {
clearInterval(timer);
reject(signal.reason);
});
});
const file = (name, size, type) => new File([new Uint8Array(size)], name, { type });
upload.addFiles([
file('launch-video-cut.mp4', 4_800_000, 'video/mp4'),
file('press-kit.zip', 2_100_000, 'application/zip'),
file('pricing-sheet-fail.pdf', 640_000, 'application/pdf'),
]);
</script>
Listen for tp-upload-complete and tp-upload-error to update your own state. Without an adapter, files stay selected and you can drive their status with setItemState().
upload.uploader = async (item, { signal, onProgress }) => {
const body = new FormData();
body.append('file', item.file);
const response = await fetch('/upload', { method: 'POST', body, signal });
if (!response.ok) throw new Error(`Upload failed (${response.status})`);
return response.json();
};
onProgress(loaded, total) takes bytes, or a percentage without total; fetch has no upload progress, so use XMLHttpRequest when you need it. Removing a file, clearing, reset and disconnection abort its upload.
Forms
Inside a Field, the label names the control, required reports a missing file and rejected files show their message. Each accepted file is appended to the form data under name, and Reset clears the files. When your server refuses a file after upload, rejectFile() moves it to the error items.
Field and form
Default density
Compact density
Restart from the initial state
Show the copyable source
Here the server check runs right after the files are added: rejectFile() turns the duplicate into an error item with your message, and it blocks submission until it is removed.
Server rejection
Default density
Compact density
Restart from the initial state
Show the copyable source
<tp-file-upload id="file-upload-server" name="invoices" multiple accept="application/pdf" size="sm" style="inline-size:100%;max-inline-size:30rem;margin-inline:auto"></tp-file-upload>
<script type="module">
await customElements.whenDefined('tp-file-upload');
const upload = document.querySelector('#file-upload-server');
const invoice = (name) => new File(['%PDF-1.7'], name, { type: 'application/pdf' });
const { accepted } = await upload.addFiles([invoice('INV-2048.pdf'), invoice('INV-2031.pdf')]);
// A simulated server already holds INV-2031 and refuses it a moment later.
setTimeout(() => {
const duplicate = accepted.find((file) => file.name === 'INV-2031.pdf');
if (upload.isConnected && duplicate) upload.rejectFile(duplicate, 'INV-2031 was already submitted on Oct 2.');
}, 800);
</script>
readonly keeps the control focusable and its files submitted, but refuses changes and hides Remove; disabled refuses every change and submits nothing.
An enclosing Field shows that message, required reports a missing file, and server errors keyed by name come from the Field errors property.
Paste
<!-- Screenshots pasted while the grid is focused or under the pointer -->
<tp-file-upload multiple layout="grid" accept="image/*" paste="control"></tp-file-upload>
<!-- Files pasted anywhere on the page, except into text fields -->
<tp-file-upload multiple paste="document"></tp-file-upload>
Paste is off by default. A paste that carries files goes through the same checks as a drop and is reported with reason input-paste and source paste; a paste without files, a paste into a text field or editable content outside the control, and a paste another handler already cancelled are left alone. Exactly one control takes each paste: the one that has focus, then the one under the pointer, then the document control that was focused, pressed or connected most recently. A disabled control takes no pastes; a read-only or full control takes the paste and refuses its files. Pasted screenshots usually arrive named image.png.
API Reference
Every element also accepts motion-policy.
Attributes
AttributePropertyTypeDefaultDescriptionmultiplemultiplebooleanfalseAllows several files; without it a new file replaces the current one.acceptacceptFileAcceptInput''Host accept text, a token list, or a record from media type to extensions.max-filesmaxFilesnumber | nullnullCount limit with multiple; null is unbounded.max-file-sizemaxFileSizenumber | nullnullLargest accepted file, in bytes or as size text such as 10MB.min-file-sizeminFileSizenumber | nullnullSmallest accepted file, in bytes or as size text.max-total-sizemaxTotalSizenumber | nullnullLargest total size of the accepted files, in bytes or as size text.allow-duplicatesallowDuplicatesbooleanfalseAccepts a file with the same name, size and modification time as one already accepted.keep-rejectedkeepRejectedbooleantrueKeeps rejected files as error items until they are removed; off, rejections are only announced
and reported.allow-dropallowDropbooleantrueAccepts files dragged onto the drop surface.prevent-document-droppreventDocumentDropbooleantrueStops a file dropped next to the drop surface from opening in the browser tab.pastepasteFileUploadPaste'none'Clipboard paste scope: none, control (while focus is in the control or the pointer is
over it) or document (also pastes elsewhere on the page outside editable fields).directorydirectorybooleanfalsePicks and reads whole folders, keeping each file's relative path.capturecaptureFileUploadCapture''Which camera the picker prefers on mobile devices: user or environment.layoutlayoutFileUploadLayout'stack'Lists files as rows (stack) or as image tiles with an Add tile at the end (grid).sizesizeFileUploadSize'default'Density of the drop surface.item-sizeitemSizeFileUploadItemSize'default'Density of the listed files in the stack layout; xs wraps them as chips.hint-treatmenthintTreatmentFileUploadHintTreatment'text'Shows the accepted types and size as a sentence, as a row of Badges, or not at all.show-itemsshowItemsbooleantrueRenders the list of files; turn it off to render your own from items.show-previewsshowPreviewsbooleantrueShows image files as Image previews instead of a type mark.show-triggershowTriggerbooleanfalseShows a Browse Button in the drop surface, as a visual cue of the click that opens the picker.removableremovablebooleantrueGives each file a Remove Button.retryableretryablebooleantrueGives each failed upload a Retry Button.clearableclearablebooleanfalseAdds a Clear all Button beside the file count.auto-uploadautoUploadbooleantrueStarts uploads as soon as files are accepted; off, call upload().upload-concurrencyuploadConcurrencynumber3How many uploads run at once.localelocalestring | undefined—Formatting locale; defaults to the nearest lang.
Properties
PropertyTypeDefaultDescriptiondefaultValuereadonly File[][]Files restored on form reset; also the initial value until files are assigned.fileValidatorFileValidator | undefined—Application validation and metadata extraction, run after the built-in checks.getItemDescription((item: FileUploadItem) => string) | undefined—Replaces the generated item description.uploaderFileUploader | undefined—Optional transfer adapter; without it the control never uploads.messagesPartial<FileUploadMessages>{}Partial text overrides; missing entries use DEFAULT_FILE_UPLOAD_MESSAGES.fieldValueread-onlyreadonly File[]—The Field value channel.itemsread-onlyreadonly FileUploadItem[]—Every item: accepted in value order, then validating, then rejected.rejectionsread-onlyreadonly FileRejection[]—Rejected files currently kept as items.fullread-onlyboolean—True while the count limit is reached (multiple selection only).dropTargetread-onlyboolean—True while a native transfer carrying files is over the Dropzone.
Events
EventTypeDescriptiontp-value-changeTpValueChangeEvent<readonly File[]>Cancelable, before the accepted files change; detail.value, detail.previousValue, detail.reason, and detail.metadata with the added, removed and rejected files and their source. Cancelling discards the batch.tp-file-rejectCustomEvent<{ rejections: FileRejection[]; source: FileUploadSource; }>{ rejections, source } once per batch with rejected files, and after rejectFile().tp-upload-startCustomEvent<{ item: FileUploadItem; }>{ item } when an upload begins.tp-upload-completeCustomEvent<{ item: FileUploadItem; response: unknown; }>{ item, response } when the upload adapter fulfills.tp-upload-errorCustomEvent<{ item: FileUploadItem; error: unknown; }>{ item, error } when the upload adapter fails.tp-file-retryCustomEvent<{ item: FileUploadItem; sourceEvent: Event; }>Cancelable { item, sourceEvent } when an item's Retry Button is pressed; by default the upload restarts.tp-picker-cancelCustomEvent<{ sourceEvent: Event; }>{ sourceEvent } when the picker closes without a selection.tp-field-valueCustomEvent<{ value: readonly File[]; previousValue: readonly File[]; reason: ChangeReason; sourceEvent: Event; cancelled: boolean; allowPropagation: boolean; }>After the accepted files change; an enclosing Field tracks its dirty state from it.tp-field-validityCustomEvent<{}>When the kept rejected files change without a value change, so an enclosing Field shows the new validity.
Methods
MethodReturnsDescriptionopenFilePicker()booleanOpens the host picker. Returns false when the control is disabled, read-only or full.addFiles(files)Promise<FileAddResult>Adds files through the validation pipeline with reason imperative-action.removeFile(target)booleanRemoves one item. Accepted files propose a value change; returns whether it was removed.clearFiles()booleanRemoves every item after one value-change proposal.rejectFile(target, errors)booleanMoves an accepted file to the rejected items, as for a server-side rejection.setItemState(target, patch)booleanUpdates an accepted item's status, progress, errors, description or metadata.upload(target?)booleanStarts queued or failed uploads (one item or all) through the upload adapter.cancelUpload(target?)voidAborts an upload (one item or all); cancelled items return to queued.validate()ValidationRunRe-runs type, size, count and custom validation over the value (Foundation fup-validate).
Slots
SlotDescriptiondefaultExtra content of the drop surface.mediaThe mark of the drop surface, in place of the upload icon.titleThe title of the drop surface, also the input's name when no Field labels it.descriptionReplaces the generated hint sentence.hintReplaces the generated row of Badges.summaryExtra content beside the file count.
CSS parts
PartDescription::part(file-upload)The root around the drop surface, the summary and the files.::part(file-upload-clear)The Clear all Button.::part(file-upload-description)The hint sentence of accepted types and size.::part(file-upload-dropzone)The dashed drop surface that opens the picker.::part(file-upload-hint)The row of Badges of accepted types and size.::part(file-upload-input)The visually hidden native file input, which takes focus.::part(file-upload-item)One file, as an Attachment or an image tile.::part(file-upload-item-delete)The Remove Button of a file.::part(file-upload-item-preview)The Image preview of an image file.::part(file-upload-item-progress)The upload bar of a file.::part(file-upload-item-retry)The Retry Button of a failed upload.::part(file-upload-items)The list of files.::part(file-upload-media)The mark of the drop surface.::part(file-upload-summary)The file count and the Clear all Button.::part(file-upload-title)The title of the drop surface.::part(file-upload-trigger)The Browse Button of the drop surface.
CSS custom properties
PropertyDefaultDescription--tp-file-upload-icon-size—Size of the mark in the drop surface.--tp-file-upload-tile-size—Smallest width of an image tile in the grid layout.--tp-file-upload-progress-inset—Inline inset of the upload bar inside an item.