Guide
The keyboard and screen readers
Uploaders are usually rebuilt from scratch — a <div> that listens for clicks, a hidden input, a
list of <span>s — and then have accessibility bolted back on. Two of the libraries this replaces
have open issues about exactly that: a broken <label for>, and unnamed pause and cancel buttons.
The input stays the control
<input id="docs" name="docs[]" type="file">
That element is still there, still focusable, still announced as "Attachments, file upload button",
and still what a form posts. The drop zone is its <label>, so:
- Tab reaches it, because it is a real input.
- Enter or Space opens the file picker, because that is what a file input does.
- Clicking anywhere in the zone opens the picker, because a label does that.
- A screen reader reads the label you already wrote for it.
Nothing here needed a tabindex, a role="button", or a keypress handler — and so there is nothing
to get subtly wrong.
The list
| Part | What it is |
|---|---|
| The list | A <ul> labelled "Files". |
| Each row | The name, the size, and what is happening to it, as text. |
| Progress | A real role="progressbar" with aria-valuenow, labelled with the file's name. |
| Remove, retry, cancel | Real buttons, each named after its file — "Remove: invoice.pdf" — so a screen reader user never has to guess which row they are on. |
| A refusal | Written in the row, in words: "Too big — 9 MB, and the most is 5 MB". |
| Announcements | A role="status" live region says how many have gone up. |
Nothing depends on a colour
- A refused row has an icon and a message, not only a red border.
- A finished row has a tick, not only a green one.
- Focus rings are drawn on every control.
@media (forced-colors: active)gives the zone and the progress bar system colours.- The dragging state changes the border style as well as its colour.
prefers-reduced-motionturns off the progress transition.
Right to left
The whole layout is written in logical properties — padding-inline, margin-block-start,
inline-size — so a page with dir="rtl" mirrors it with nothing to configure. Sizes and times come
from Intl, so they are already in the right script and the right order. This is an open request on
the uploader with sixteen thousand stars, and it costs nothing.
What is announced, and what is not
Progress is deliberately not announced as it moves: a live region firing twenty times a second makes a page unusable. The live region speaks when a file is added, when one finishes, and when one is refused — the three things worth interrupting for. The progress bar itself carries the current value for anyone who goes looking.