File dropzone
A file field written as plain markup: a zone, a real file input inside it, a line of help.
Documentation
-
Copy to Webflow
Add Geist and Geist Mono to your site's fonts (Site settings, Fonts, Google Fonts) before pasting: Webflow removes any font your site has not installed. -
Add custom CSS
Custom CSS in WebflowThis CSS already ships inside the Webflow embed, so a pasted section needs nothing added. Paste it yourself, in the page or the site custom code, only if the effect runs on several pages. The tutorial explains where.
See the tutorialThe look of the preview is thefile-dropzone-*classes of the paste: the zone at rest, its sentence, the label and the hint. The Embed holds what moves, the sheet and the lines of the list, and reads four variables,--file-dropzone-ink,--file-dropzone-ground,--file-dropzone-accentand--file-dropzone-error: set them on the field, or on anything above it, in a rule of your own. The words on the sheet take the type of the zone, and its top and bottom padding. -
Add custom JS
Custom JavaScript in WebflowThis script already ships inside the Webflow embed, settings block included. You paste it yourself, before the closing body tag of the page or the site custom code, only when the effect has to run on several pages, and the tutorial says how to choose.
See the tutorial -
Use it on your own elements
How to add a custom attribute in WebflowA custom attribute is a pair, a name and a value, typed in the Settings panel of the element you selected, under Custom attributes. The name is what the script looks for, the value is what it reads. The tutorial shows the panel.
See the tutorialCustom attributesSelect the element on the canvas and open the Settings panel. Under Custom attributes, add a pair: the name on the left, the value on the right.Custom attribute What it does data-file-dropzone Marks the field: the block that holds the zone, the list of files and the hint. It takes no value: leave the value field empty. The settings below go on the same element. data-file-dropzone-zone On the box a file is dropped on, which holds the file input. It takes no value. The engine lays the input over it, so a click anywhere opens the picker, and draws the sheet that rises in it. Its top and bottom padding are the sheet's. Without it the parent of the input is taken. [ Required ] data-file-dropzone-label On the words that name the field, when they are not a label element, which Webflow only has inside a form. It takes no value. The engine names the input by them for a screen reader. [ Optional ] data-file-dropzone-hint On the line of help. It takes no value. The reason takes its place when a file is refused, then the hint comes back. A field without one gets a line from the engine. [ Optional ] data-file-dropzone-pointer On words that only make sense with a mouse, "or drop them here". It takes no value. They are left out on a touch screen. [ Optional ] data-file-dropzone-list Optional, on a list of your own where the lines of the files go. It takes no value. Without it the engine adds the list under the zone. [ Optional ] data-file-dropzone-state Written by the engine on the field: focus, filled, busy, error, disabled, and during a drag armed, then over or refuse, several at once. Each line of the list carries data-file-dropzone-row: loading, done, failed or out. Never written by hand; the stylesheet reads them, and so can a rule of yours. [ Optional ] SettingsEvery setting is a custom attribute too, added the same way and on the same element. An attribute always wins over the defaults written in the code.Attribute Default What it is data-file-dropzone-max-size 0 The largest file the field takes, in megabytes (1 MB is 1 000 000 bytes). A heavier file is refused with the words of size. data-file-dropzone-max-files 0 The most files the field holds at once, when its input carries multiple. The files beyond it are refused with the words of count. data-file-dropzone-upload empty The address each file is posted to as soon as it is added, alone, as multipart/form-data under the name of the input. Empty, the default: nothing is posted, the files stay in the input and leave with the form. Options: empty; or an address, https://example.com/upload, which must answer in the 200s and allow the page's origin. data-file-dropzone-paste field Where a file pasted from the clipboard, a screenshot for one, is heard. Options: field, the default, when the focus is in the field; page, anywhere on the page, the file going to the field that has the focus or to the first field set to page; off, never. data-file-dropzone-drop Release to add The words in the open slot, while a file is held over the zone. Options: any text, in the language of the page. data-file-dropzone-type {name} is not an accepted type. The reason under the field when a file is not of an accepted type. {name} is the name of the file. Options: any text, in the language of the page. data-file-dropzone-size {name} is over {max}. The reason under the field when a file is over maxSize. {name} is the name of the file, {max} the limit as it is read, 10 MB. Options: any text, in the language of the page. data-file-dropzone-count {max} files at most. The reason under the field when more files are offered than maxFiles. {max} is the limit. Options: any text, in the language of the page. data-file-dropzone-required Add a file. The reason under a field whose input carries required, when its form is sent with no file. Options: any text, in the language of the page. data-file-dropzone-busy Wait for the files to finish. The reason under the field when its form is sent while a file is still uploading. Options: any text, in the language of the page. data-file-dropzone-failed Failed The word on the line of a file whose upload failed, when no reason was given. Options: any text, in the language of the page. data-file-dropzone-retry Retry The word of the button that sends such a file again. Options: any text, in the language of the page. data-file-dropzone-remove Remove The name of the cross that removes a file, read aloud by a screen reader before the name of the file. Options: any text, in the language of the page. The way the files are sentThis field is not Webflow's own File upload button. That button, Webflow's help says, exists on the Premium (legacy Business), Ecommerce Plus and Ecommerce Advanced site plans and "accepts a single file of up to 10MB"; this field is one input that holds several files. The paste is plain blocks, with the file input in a small Embed inside the zone: open that Embed to change itsname,accept,multipleorrequired. Drag the field into a Form block of your own, then choose how the files leave. In the form's settings, under Send to, a Custom action with the method POST hands the form to the address you give, a form service or an endpoint of your own that accepts files, and bypasses Webflow's own handling. Or adddata-file-dropzone-uploadon the field with the address that receives each file as it is added. Neither depends on the site plan. Do not count on a form that Webflow collects itself to carry the files: Webflow documents only its own button for that, and this was not tried on a live site. The preview sends nothing.Custom attribute What it does data-file-dropzone-upload="https://..." On the field: posts each file to that address as soon as it is added, alone, as multipart/form-data under the name of the input, with the header Accept: application/json. An answer in the 200s shows the file as taken and puts the url or the id of a JSON answer, or the answer as text, in a hidden field of the same name; anything else shows Failed and a Retry. The address must allow the page's origin. data-file-dropzone-max-size="10" On the field: the largest file taken, in megabytes. HTML has no attribute for it. data-file-dropzone-max-files="5" On the field: the most files held, for an input that carries multiple. HTML has no attribute for it. accept, multiple, required, name On the input itself, as on any file input: the types taken, several files or one, a field that cannot be left empty, and the name the files are sent under. In Webflow the input is in the small Embed inside the zone.
Two files, no build step.
It runs in the browser only. Load it with a script tag in plain HTML, in a
client-side script in Astro, never in the frontmatter. In React, call
FileDropzone.mount(ref.current) in useEffect and
destroy() in its cleanup.
-
Tune it, either way
Per element with an attribute, or once for the whole site by editingDEFAULTSat the top of the file.Attribute Default What it is Lower Higher data-file-dropzone-max-size 0 The largest file the field takes, in megabytes (1 MB is 1 000 000 bytes). A heavier file is refused with the words of size. Lighter files only; 0.5 is 500 KB; 0, the default, sets no limit Heavier files pass. Stay under what the receiving end accepts data-file-dropzone-max-files 0 The most files the field holds at once, when its input carries multiple. The files beyond it are refused with the words of count. Fewer files; 0, the default, sets no limit More files. An input without multiple holds one file whatever this says, and a new file replaces it data-file-dropzone-upload empty The address each file is posted to as soon as it is added, alone, as multipart/form-data under the name of the input. Empty, the default: nothing is posted, the files stay in the input and leave with the form. Options: empty; or an address, https://example.com/upload, which must answer in the 200s and allow the page's origin. data-file-dropzone-paste field Where a file pasted from the clipboard, a screenshot for one, is heard. Options: field, the default, when the focus is in the field; page, anywhere on the page, the file going to the field that has the focus or to the first field set to page; off, never. data-file-dropzone-drop Release to add The words in the open slot, while a file is held over the zone. Options: any text, in the language of the page. data-file-dropzone-type {name} is not an accepted type. The reason under the field when a file is not of an accepted type. {name} is the name of the file. Options: any text, in the language of the page. data-file-dropzone-size {name} is over {max}. The reason under the field when a file is over maxSize. {name} is the name of the file, {max} the limit as it is read, 10 MB. Options: any text, in the language of the page. data-file-dropzone-count {max} files at most. The reason under the field when more files are offered than maxFiles. {max} is the limit. Options: any text, in the language of the page. data-file-dropzone-required Add a file. The reason under a field whose input carries required, when its form is sent with no file. Options: any text, in the language of the page. data-file-dropzone-busy Wait for the files to finish. The reason under the field when its form is sent while a file is still uploading. Options: any text, in the language of the page. data-file-dropzone-failed Failed The word on the line of a file whose upload failed, when no reason was given. Options: any text, in the language of the page. data-file-dropzone-retry Retry The word of the button that sends such a file again. Options: any text, in the language of the page. data-file-dropzone-remove Remove The name of the cross that removes a file, read aloud by a screen reader before the name of the file. Options: any text, in the language of the page. The way the files are sentThe files dropped, pasted or picked are written into the input, so a form sends exactly the list on screen. They leave in one of three ways. With the form: a plain post, ornew FormData(form)in a fetch; a form that posts and names no encoding is givenmultipart/form-data. One by one, withdata-file-dropzone-upload: each file is posted to that address as soon as it is added, and a hidden field of the same name then carries theurlor theidof the answer in its place. Or by the page: a listener onfiledropzone:uploadcallspreventDefault(), sends the file itself and reports withdetail.progress(),detail.done()ordetail.fail(). The preview page does the last with a timer and sends nothing.Custom attribute What it does data-file-dropzone-upload="https://..." On the field: posts each file to that address as soon as it is added, alone, as multipart/form-data under the name of the input, with the header Accept: application/json. An answer in the 200s shows the file as taken and puts the url or the id of a JSON answer, or the answer as text, in a hidden field of the same name; anything else shows Failed and a Retry. The address must allow the page's origin. data-file-dropzone-max-size="10" On the field: the largest file taken, in megabytes. HTML has no attribute for it. data-file-dropzone-max-files="5" On the field: the most files held, for an input that carries multiple. HTML has no attribute for it. accept, multiple, required, name On the input itself, as on any file input: the types taken, several files or one, a field that cannot be left empty, and the name the files are sent under. In Webflow the input is in the small Embed inside the zone. -
Drive it from JavaScript
When the page sends the files itself, fills the field from a script, or reacts to its list changing.const element = document.querySelector('[data-file-dropzone]'); element.addEventListener('filedropzone:change', (e) => { console.log(e.detail); }); // Send each file yourself: its line counts until done() or fail() is called. element.addEventListener('filedropzone:upload', (event) => { event.preventDefault(); const { file, progress, done, fail } = event.detail; const request = new XMLHttpRequest(); const body = new FormData(); body.append('file', file); request.open('POST', '/api/upload'); request.upload.onprogress = (e) => progress(e.loaded / e.total); request.onload = () => (request.status < 300 ? done(request.responseText) : fail()); request.onerror = () => fail(); request.send(body); }); const field = FileDropzone.mount(element); // the field auto() mounted, or a new one field.files(); // the files held field.validate(); // shows the reason, returns true when the form can leave field.clear(); // When the field leaves the page: FileDropzone.destroy(element); // removes what the engine added, gives the markup back as it was
-
Fit it to your page
The markup
html <div data-file-dropzone data-file-dropzone-max-size="10" data-file-dropzone-max-files="5"> <label for="files">Attachments</label> <div data-file-dropzone-zone> <div>Choose files <span data-file-dropzone-pointer>or drop them here</span></div> <input id="files" type="file" name="attachments" multiple accept="application/pdf,image/*"> </div> <div data-file-dropzone-hint>PDF or pictures. 10 MB each, five files at most.</div> </div>- The field stands alone or in a form, and as many times as needed on a page: each field keeps its own files, limits and words.
- The input is the field. With the engine it lies over the whole zone, unseen: a click or a tap anywhere opens the browser's picker, Tab reaches it, Space or Enter opens the picker, and a screen reader announces a file field with its label and its hint. Without JavaScript it is shown in the zone as the browser draws it, and the field still works.
- The files dropped, pasted or picked are written into the input, added to the ones it holds. So
new FormData(form), a plain post of the form and the input's ownfilesall give exactly the list on screen. - A refused file never enters the list. Its reason is written in the line of help, in the colour of an error, and the zone shakes once. Two reasons at most are shown at a time; the line goes back to the hint after eight seconds or at the next file.
- While a file is dragged, the zone already knows the types the hand holds when
acceptonly names types or families (application/pdf,image/*), and shows its edge in the colour of an error instead of opening. With extensions inaccept(.pdf) the browser gives no name before the drop, so the zone opens and the file is refused on release. - A file let go beside the field is not opened by the browser in place of the page: while a field is mounted, a drop of files outside a zone does nothing, unless another script of the page takes it.
How the files leave
Three ways, and the preview is none of them: it sends nothing, and the script at the end of its markup only stands in for an upload.
- With the form, the default. The files are in the input; the form posts them under its name. A form whose method is
postand that names no encoding is givenenctype="multipart/form-data"by the engine, without which a browser sends the names of the files and not the files. - To an address, one by one, with
data-file-dropzone-upload. Each file is posted as soon as it is added, its name inked as the request progresses. Once the address has answered in the 200s the file leaves the input, and a hidden field of the same name takes its place in the form, holding theurlor theidof a JSON answer, or the answer as text. An input emptied that way gives up itsnameuntil it holds a file again, so the form does not send an empty file beside the values. Any other answer shows the file as failed, with a Retry. - By the page, with a listener on
filedropzone:uploadthat callspreventDefault(), sends the file as it likes, and reports withdetail.progress(0.4),detail.done()ordetail.fail('Too large').done('https://...')with a value replaces the file in the form by that value, as the address does;done()with none leaves the file in the input.
A form is kept on the page while a file is still uploading, with the words of
busy, and while a required field is empty, with the words ofrequired. The engine hears the submit on the document, before the form's own listeners, so a form of the Form set never shows itself as sending.In Webflow
What Webflow's help centre says, read on 11 October 2026 (articles "Form file upload" and "How do I add forms in Webflow?"):
- Webflow has its own File upload button, a form element available "on the Premium Site plan (or legacy Business Site plan), Ecommerce Plus Site plan, or Ecommerce Advanced Site plan".
- "Each file upload button accepts a single file of up to 10MB in size", from a list of formats; a form holds up to 20 of them. It has four states of its own, Default, Uploading, Success and Error, each styled in the Designer.
- The files it collects are listed in Site settings, Forms, and sent as a link in the notification mail. Storage is free up to 10 GB.
- A form can instead be sent to a Custom action: an address and a method, POST or GET, set under Send to in the form's settings, which "bypass[es] Webflow's built-in form handling entirely": the submission is not sent to or stored in Webflow, and the setting writes the
actionandmethodattributes of the form.
This component does not use Webflow's button: it is one file per button and a block with its own structure, where this field is one input that holds several files. So the field of the paste is plain blocks, with the
<input type="file">in a small Embed inside the zone, where itsname,accept,multipleandrequiredare edited. What follows:- In a form sent to a Custom action that accepts files (a form service, an endpoint of your own), with the method POST, the form is the browser's to post, with its files. The engine adds
enctype="multipart/form-data"to such a form; Webflow's help names no setting for it. Neither this nor the next route depends on the site plan. - Or post each file as it is added, with
data-file-dropzone-uploadon the field, to an address of your own. - In a form that Webflow collects itself, do not count on the files. Webflow's help describes its own button as the way a form collects a file and says nothing of a file input added by hand; this is a deduction from that silence, it was not tried on a live site. The boot of the Embed warns in the console when it finds the field in such a form.
To confirm on the first real paste: that the Designer takes the small Embed inside the zone and shows the input on the canvas, and that a form with a Custom action posts its files.
The keyboard
Tab reaches the input: the rule under the zone is drawn in ink. Space or Enter opens the picker. Tab again goes through the files, to the Retry of a failed one and to each cross; Enter or Space removes the file, and the focus goes to the next cross, or back to the input after the last.
The colours and the sizes
Four custom properties, set at zero specificity on the field. Give them on the field or on anything above it:
| Property | What it colours | |---|---| |
--file-dropzone-ink| The sheet, the names, the focus line | |--file-dropzone-ground| The words on the sheet | |--file-dropzone-accent| The number of files on the sheet | |--file-dropzone-error| A reason, a failed file, the edge of a refusal |The rule under the zone is the border of the zone's own class.
--file-dropzone-insetis the margin of the words inside the sheet. The words of the sheet take the type of the zone; the lines of the list are set at 19 px, the size of a field of the Form set.From a script
js const field = FileDropzone.mount(element); field.files(); // the files held, in order field.add(fileOrFiles); // as if they had been dropped field.remove(0); // by index, by name or by file field.clear(); field.validate(); // shows the reason, returns true when the form can leave field.over(2); field.over(false); // shows the open slot, for a preview field.destroy(); // gives the markup backEvents, all bubbling from the field:
filedropzone:ready,filedropzone:change(detail.files),filedropzone:refuse(detail.file,detail.reason:type,sizeorcount,detail.words),filedropzone:remove,filedropzone:upload(cancelable, see above),filedropzone:uploaded,filedropzone:failed,filedropzone:invalid. The input also fires its ownchangeeach time the list changes. -
Avoid the pitfalls
- The zone needs a position of its own. The mechanics give it
position: relativeat zero specificity; a rule of the page that sets it back tostaticlets the input and the sheet cover the wrong box. - The padding of the zone is the padding of the sheet, top and bottom, so the words of the drop sit on the line of the sentence. Put the padding on the zone, not on what it holds.
- **An input inside a
<label>** is fine; an input outside the zone is not laid over it, and only a drop reaches the field. - **
acceptis a filter the browser's picker lets a visitor switch off.** The engine checks every file again, but the receiving end must check too: a type and a size read in the browser are a courtesy, not a control. - Sizes are decimal: 10 MB is 10 000 000 bytes, the way a Mac and a phone show them.
- A browser too old to let a script write an input's files (Safari before 14.1) keeps the picker and ignores a drop, and says so in the console.
- A folder dropped on the zone is refused like a file of a wrong type when
acceptis set; withoutacceptit enters as an empty file. Setaccept. - Several fields and one paste: a pasted file goes to the field that has the focus. Set
pastetopageon one field only.
- The zone needs a position of its own. The mechanics give it
One click on Copy the AI prompt copies everything an assistant needs to build this effect: the complete code, the markup, the dependency, every setting and every pitfall.
What it does
File dropzone is an animated form for Webflow and vanilla JavaScript, with no library. Also called drag and drop file upload, multiple file upload or custom file upload field.
A file field written as plain markup: a zone, a real file input inside it, a line of help. At rest it is one sentence on a rule, in the register of the Form set. A file dragged over the page makes a sheet of ink show its edge under the rule; over the zone the sheet rises to the top with the words of the drop and the number of files in the hand; on release it falls back and each file comes out from under the rule as a line of the list. There is no progress bar: the name of the file is grey and is inked from the left as far as the upload has gone, while a small count runs beside it and becomes the size and a drawn tick. A file is removed by a cross, its name struck through before the row closes. Types and number come from the input's own accept and multiple, weight and count from two attributes; a refused file never enters the list, and the reason is written under the field. The input is the browser's own, so the keyboard, a screen reader, a form and a page without JavaScript all use it as any file field; the files dropped or pasted are written into it, so a form sends exactly what the list shows. Each file can also be posted to an address as it is added. Reduced motion respected. No library. Pattern type file upload, dropzone, attachment field.
Updated
Questions
Webflow's own File upload button takes one file each. This field is a single <input type="file" multiple>, placed in a small Embed inside a block marked data-file-dropzone-zone: it holds as many files as data-file-dropzone-max-files allows, and lists them under the zone. Send the form to a Custom action that accepts files, or post each file to an address with data-file-dropzone-upload.
Webflow's File upload button belongs to the Premium, Ecommerce Plus and Ecommerce Advanced site plans. This field does not use it, so it does not depend on the plan, but Webflow then does not store the files: they go to the address of a Custom action, or to the one named in data-file-dropzone-upload.
The types are the input's own accept attribute, accept="application/pdf,image/*", checked again on a drop and on a paste. The weight is data-file-dropzone-max-size, in megabytes, and the number of files data-file-dropzone-max-files, both on the field. A refused file never enters the list, and the reason is written under the field. The receiving end must check too.
Here the name of each file is the progress: it is grey when the file is added and is inked from the left as far as the upload has gone, beside a small count that becomes the size and a tick. With data-file-dropzone-upload the engine follows the real request; a page that sends the file itself reports with detail.progress() from the event filedropzone:upload.
The zone is a real file input laid over it, not a div with a click handler: Tab reaches it, Space or Enter opens the browser's picker, and a screen reader announces a file field with its label and its hint. Each file has a remove button named after it, refusals are announced, and without JavaScript the browser's own control shows.
Yes. With the focus in the field, a file pasted from the clipboard is added like a dropped one and goes through the same checks. data-file-dropzone-paste="page" takes a paste made anywhere on the page, and off turns it off. Pasted text is never touched.