David Maillo

The File Uploader the Web Deserves

AI lets us build more than ever. It can also help us build better, if we point it at the small pieces nobody has time for. The file uploader is one of those pieces.

More, and faster

Something changed in how we build software. A feature that took a week takes an afternoon. A prototype that took a month takes a weekend. With an AI agent next to you, the cost of making one more thing has dropped close to zero.

Most of that new speed goes into volume. More features, more screens, more products. That's fine. Shipping things people want is the job.

Shipped this sprint0
With an agent next to you, one more screen costs almost nothing.

But there's another way to spend it.

Or better

The same tools that let us build more can help us build things properly. The second pass nobody had time for. The edge case you knew about and never fixed. The animation that makes a moment feel right. AI doesn't only write the first draft. It's very good at finding what's wrong with one, and at helping you polish it until nothing is.

upload.tsx · second passReviewing
    0 found0 fixed
    The same tools that write the first draft are good at finding what's wrong with it.

    Attention to detail used to be a luxury, because it cost time you didn't have. It costs a lot less now. What's missing is someone deciding to spend it.

    The piece every sprint rushes

    Think about how many places an app ends up needing uploads. The profile picture. Attachments in a message. Documents in a form. A CSV import. A logo in the settings page. It keeps coming back.

    And in every sprint, it gets the same treatment. It hides inside another ticket. It gets half a day. Someone builds the minimum: an input, a request, a success message. It works with the three files on the developer's desktop, passes review, and never gets touched again. Nobody stops to make a good one, because there's always something bigger to build.

    Sprint boardThree weeks later
    To do
    Users can upload a file½ day
    In progress
    Done
    It gets half a day, it ships the minimum, and the bugs arrive later, one report at a time.

    The minimum ships with the same holes every time:

    And it looks like the minimum

    The bugs are half of it. The other half is how it feels.

    The minimum uploader is a grey dashed rectangle and a "Choose file" button. No drag state, no progress, no sign that anyone thought about the moment. And it's a moment that matters. The upload box is often in the first minutes of a product: the profile picture during onboarding, the first document a customer sends you. It's one of the few places where people hand your app something of their own.

    When that moment feels careless, people notice, even if they couldn't tell you why. When it feels good, they notice that too.

    So I stopped and built a good one

    Drop Upload is what happens when someone gives the uploader the time a sprint never does. I designed and built it by hand, then used AI the way I described above: to hunt down edge cases, question every state, and harden each fix. The result is a drag and drop file upload component for React that has already been through every bug on that list, with the source explaining each fix in its comments.

    The minimum
    With care
    Try dropping a few files
    image files, video files, or PDF. Max: 10 MB. Up to 4 files
    Both are real, so try each one. Uploads on the right are simulated, and nothing leaves your browser.

    Validation that matches its own copy

    You set allowedFormats, maxFileSize and maxFiles once. The same props drive the checks and the instruction text, so what the box says and what it enforces can't drift apart. It reads each file's first bytes to catch a renamed file, turns folders away with a clear reason, and reports every rejection to your code through onFilesRejected.

    These checks save people from waiting on an upload that was never going to work. Your server should still validate again, and the docs say so.

    Uploads that go where your files already live

    Five adapters come in the box: your own REST endpoint, S3, S3 multipart for large files, Supabase and Cloudinary. Each one has real progress, cancel and retry. Every file gets its own AbortController, so cancelling one leaves the rest of the queue alone.

    An avatar uploader in the same box

    AvatarUpload turns the drop area into a circle, with crop and zoom inside it before anything uploads. It shares the same engine as the main component, so validation, adapters, callbacks and languages behave the same in both.

    The details that never make the roadmap

    Paste to upload with Ctrl+V. Hold to discard, so nobody loses a file by accident. Real video thumbnails. A stacked list that doesn't push your form down the page. Full keyboard support. Every animation respects reduced motion. English, Spanish, French and German built in, and every string can be reworded. Light and dark themes, passed in as a prop so it renders the same on the server and never flashes.

    And a small puff of smoke where a file lands. Nobody asks for it. People notice anyway.

    Why it fits a React app

    Drop Upload is written the way React code is meant to be written. You describe the uploader with props and hear from it through callbacks. You never touch a DOM node or wire up a drag listener yourself.

    import { DropUpload, createRestAdapter } from "./components/DropUpload"
    
    const adapter = createRestAdapter({ endpoint: "/api/upload" })
    
    export default function Page() {
      return (
        <DropUpload
          allowedFormats={["image/*", "application/pdf"]}
          maxFileSize={5 * 1024}
          maxFiles={3}
          uploadAdapter={adapter}
          onUploadComplete={(file) => console.log(file.url)}
        />
      )
    }

    When your app needs to be in charge, a ref gives you addFiles, removeFile, clearFiles, startUpload and cancelUpload. Set autoUpload={false} and files wait until the form is submitted.

    Spend the speed on something

    Your AI agent can write an upload box in a minute. That's the minimum again, just faster. Give it Drop Upload instead, and it spends that minute wiring a finished component into your app. The docs have prompts that work.

    Then use the time you saved on the next small piece that deserves better.

    Throw some files at the demo above, or open the playground and change every prop. The docs cover setup, adapters and customisation, and the pricing page has the rest.