UIDex

File Upload / Dropzone

رفع الملفات

<input type="file">

also calleddropzone، file picker، file input، upload field، drag and drop upload، attachment field، منطقة الإفلات، حقل رفع الملف، دروب زون، أبلود، سحب وإفلات الملفات، حقل المرفقات

File upload is how a user hands a file from their own machine to the application, and on the web it rests on one element: <input type="file">. Pressing it opens the operating system picker, a window your page has no authority over: it will not open without a user gesture, it never reveals the path on disk, and all that comes back is a File object. Three things get mistaken for it. A dropzone is a drag target and not a field, a rectangle listening for dragover and drop, and it is a second route to the same files that never replaces the input, because dropping does not exist on a touch screen or from a keyboard. The "Choose a file" button is rarely its own element; it is usually a <label> bound to the input, which forwards the pointer press while the input keeps the focus and the keyboard activation, since a <label> is not in the tab order. A full uploader sits on top of all of this: a queue, per-file progress, cancel, retry. The field itself uploads nothing, it only selects, and sending the bytes belongs to fetch or XMLHttpRequest, unless the input sits inside a <form> with enctype="multipart/form-data", where the browser sends them on submit.

If you called it…

"the dotted box that says drag files here""the button that opens my folders""the attach a file thing""the place where you drop the photo""the list of file names with a little bar filling up"

Live specimen

Interact with the demo. Every part is real and numbered.

اسحب الملفات وأفلتها هنا<input type="file">
PDF أو PNG · حتى ٥ ميجابايت للملف الواحد
تقرير-الربع-الثالث.pdfتم الرفع
invoice_2026.pdf٨٦٠ كيلوبايت
RTLالاسم اللاتيني معزول بـ bdi، وزرّ الإزالة عند النهاية المنطقية

Anatomy: every part, named

Hover a row to locate it in the demo above.

Build prompt

Build a file upload field with a drop zone. Keep <input type="file"> in the DOM at all times and hide it with the sr-only pattern (absolute position, one pixel box, clip-path) rather than display:none, so it stays in the tab order and in the accessibility tree, and bind it to a <label for> whose text you write; put no <button> inside that label, and if you need a real button call showPicker() from a direct click handler. Make the drop zone call preventDefault() in both dragover and drop, count dragenter and dragleave with a depth counter so the highlight does not flicker, and cancel the default drop on window so the browser does not leave the page and open the file. Show a constraints line naming accept and the size cap, and treat accept as a filter and not as validation: check type and size after a pick and after a drop, then check them again on the server. Give every file a row with the name inside a <bdi>, the size, and its own progress bar with role="progressbar" and aria-valuenow, driving the percentage from xhr.upload.onprogress after reading lengthComputable. Give the remove button an accessible name that includes the file name, move focus after a removal to the next row or back to the trigger, and reset input.value to an empty string after every pick so the same file can be chosen twice. Use logical properties throughout: margin-inline, padding-inline, inset-inline-start, border-inline-start, and never left, right, or margin-left. Animate the bar by changing inline-size on an element pinned with inset-inline-start: 0 instead of scaleX, and format sizes with Intl.NumberFormat using one numbering system for the whole product.

How it behaves right-to-left

This section is ours alone.

The file row

mirrors

The row is ordered logically: icon and name at the start, size and the remove button at the end, so the whole row swaps in Arabic and the remove button lands on the left. Build it as a flex row with margin-inline-start: auto on the button, never margin-left: auto and never float: right. If a coloured edge marks the file status, draw it with border-inline-start rather than border-left, or after the flip the edge sits at the opposite end from the name.

The per-file bar

mirrors

The bar fills from the logical start, which in Arabic is the right. Build the fill as an inner element pinned with inset-inline-start: 0 and animate its inline-size from 0 to 100%: it mirrors on its own, because the edge it is anchored to is the one that moved. Stay off transform: scaleX(), since transform-origin accepts no logical keyword at all, and off linear-gradient(to right, …), since gradients have none either. A native <progress> takes its direction from the inherited direction value, so a dir="ltr" you add to the row to fix the file name reverses the fill along with it.

A Latin file name in an Arabic row

never mirrors

A file name is user text whose direction you cannot know in advance, so isolate it instead of forcing one: <bdi>, or unicode-bidi: plaintext on the element holding it. CSS has no direction: auto, since the property takes only ltr and rtl; the automatic behaviour lives in the dir="auto" attribute and in the plaintext keyword, which isolates the run and reads its direction from the first strong character. Without isolation the neutral characters next to digits (hyphen, underscore, dot) take the direction of the Arabic paragraph, the name breaks into islands that are reordered right to left, and invoice_2026.pdf renders on screen as pdf.2026_invoice. Parentheses make it worse, since their Bidi_Mirrored property is Yes and the glyphs themselves swap. Do not patch this with dir="ltr" on the row: that flips the layout and the bar with it, and it mangles Arabic names carrying digits such as "تقرير 2026.pdf".

Size and the numeral system

never mirrors

A file size is a number and a unit, and neither mirrors: numbers read left to right in Arabic too. The failure is mixing systems: a page that prints ٢٫٤ in Arabic-Indic digits and then MB in Latin letters, or one that builds the string with toFixed(1) and glues an Arabic unit onto it, so "2.4 ميجابايت" sits in the list next to "٨٦٠ كيلوبايت". Pick one numbering system for the whole product and format through it: Intl.NumberFormat("ar-EG", { style: "unit", unit: "megabyte", unitDisplay: "short" }) returns the number, the separator and the unit from one source, and the Arabic decimal separator is U+066B, not the dot. If you want Latin digits inside an Arabic page, ask for them explicitly with ar-EG-u-nu-latn.

The system dialog

never mirrors

The picker window is not part of your page: the operating system draws it, and its direction and language follow the device settings, not your dir attribute and not your interface language. Do not promise an Arabic dialog and do not try to style it. The default text inside the input itself (Choose File, No file chosen) comes from the browser locale and cannot be translated or replaced, and ::file-selector-button styles the button without touching its words. That alone is reason enough to hide the input behind a <label> whose text you write yourself.

In code

Each row is one framework's word for the same thing. Take the row your project speaks.

HTML<input type="file" accept multiple>The whole feature in one tag; everything else is decoration around it.
HTMLinput.showPicker()Opens the system dialog from code, and throws NotAllowedError without user activation.
HTMLevent.dataTransfer.filesWhere dropped files arrive; accept does not filter this list at all.
CSS::file-selector-buttonStyles the native button inside the input, whose text comes from the browser locale and will not change.
ARIArole="progressbar" + aria-valuenowRequired for any bar drawn from divs instead of <progress>, or the percentage is unreadable.
Ant Design<Upload.Dragger>Ships the drag area, the file list, and per-file status in one component.
Material UI<Button component="label"> + VisuallyHiddenInputTheir documented pattern: a styled button wrapping a clipped input that stays focusable.

See also

Updated · 2026-08-19