A useful file upload tells people what they can choose, what the system is doing, and whether their file is ready to use. Build those three parts together. A drop zone alone does not finish the job.
For a small SaaS, an upload may be the first time someone brings their own work into the product. That makes the details worth checking before adding a more elaborate interface.
State the rules before someone chooses a file
Put the supported formats, size limit, and file count beside the input. Use the same rules your server enforces. Avoid making people discover a limit only after a long transfer.
A hypothetical invoice tool might say: “Upload one PDF, up to 10 MB.” If a user needs several files, explain whether they can select them together or must upload them one at a time.
Use a visible label that names the task, such as “Upload an invoice.” Keep a keyboard-accessible file picker even if you also support dragging files onto the page.
Treat file selection as the start
Selecting a file does not necessarily mean it has been sent. Show the selected filename and make the next action clear. If uploading starts immediately, say so before selection. If it needs a button, use a label such as “Upload invoice.”
The HTML file input can guide selection with its accept attribute. MDN explains that this is a browser hint, not validation. The server still needs to validate the upload. A filename extension or a browser filter is not enough to establish that a file is safe or usable.
Let the user replace a mistaken selection before committing it where the workflow allows. Keep the filename visible so they can check their choice.
Write errors around the next action
“Upload failed” leaves several possible problems unresolved. Did the connection drop? Was the file too large? Was its format unsupported?
For the hypothetical invoice tool, useful messages could be:
- “This file is larger than 10 MB. Choose a smaller PDF.”
- “We couldn't upload the file. Check your connection and try again.”
- “We uploaded the PDF, but couldn't read it. Try a clearer copy.”
These messages describe different states. Only show the last one when the file really reached the service and the processing step failed.
W3C's form notification guidance recommends clear feedback with instructions for fixing errors. Keep the message near the input, associate it with the control, and make dynamic feedback available to assistive technology. Color alone should not carry the meaning.
Separate transfer from processing
If the file needs scanning, parsing, or conversion after transfer, give that work its own status. “Uploaded. Checking the invoice…” is more useful than leaving a progress bar at 100% while the page remains unusable.
Only show a percentage when you can measure the relevant progress. Otherwise, a short status message is an honest choice. Avoid announcing every small progress change to a screen reader; meaningful state changes are easier to follow.
Finish with the result and the next action: “Invoice ready. Review the extracted details.” If processing can continue after the user leaves, explain where they can find its status later.
Try the uncomfortable paths
Use this review list before calling the flow finished:
- Choose the wrong format, an oversized file, and an empty file.
- Cancel the picker and choose the same file again.
- Interrupt the connection, then try again.
- Complete the flow with a keyboard and with a screen reader.
- Check what a second click does while an upload is already running.
These are suggested checks, not reported test results. Your product may need more, including checks for its file-processing and security requirements.
For every path, ask: can the user tell what happened, keep their bearings, and take the next step? That is the standard a useful upload should meet.
Hey I'm Uriel Bitton. I write about building in public strategies and growing startups.
Subscribe for more stories on growing your audience by building in public.
Join us on Buildside: the social network for founders building in public.
Top comments (3)
The three example messages are really three different states, and I think that's where most upload UIs lose. The copy can only be as specific as what the API reports. If the backend returns one generic failure, whoever writes the UI can say "Upload failed" or guess, and guessing is worse. So the useful work usually starts in the response contract: which stage failed (transfer, validation, processing), and whether retrying the same file can help.
"Choose a smaller PDF" and "try again" are opposite instructions. Showing the wrong one sends someone to re-upload a file that will fail the same way.
Spot on. A beautiful frontend means absolutely nothing if the API contract is just a lazy HTTP 500 with a generic 'something went wrong' message. Granular error states on the backend are a prerequisite for good UX, not an afterthought.
Nothing breaks the user experience faster than waiting 2 minutes for a 50MB file transfer just to get a 'File size limit exceeded' error at 99%. Showing the exact validation rules before the upload starts is just basic respect for the user's bandwidth.