react-dropzone vs filepond vs blueimp-file-upload vs fine-uploader vs uppy vs dropzone vs ng-file-upload
前端文件上传库技术选型指南
react-dropzonefilepondblueimp-file-uploadfine-uploaderuppydropzoneng-file-upload类似的npm包:

前端文件上传库技术选型指南

blueimp-file-upload、dropzone、filepond、fine-uploader、ng-file-upload、react-dropzone 和 uppy 都是用于处理 Web 应用中文件上传功能的 npm 包,但它们在框架兼容性、功能集、UI 完整度和架构设计上存在显著差异。这些库提供了从基础拖拽上传到高级分片、断点续传等能力,适用于不同技术栈和项目复杂度的需求。

npm下载趋势

3 年

GitHub Stars 排名

统计详情

npm包名称
下载量
Stars
大小
Issues
发布时间
License
react-dropzone14,028,09811,017343 kB120 小时前MIT
filepond296,31016,3921.2 MB1458 个月前MIT
blueimp-file-upload100,48930,691-535 年前MIT
fine-uploader40,4688,137-1229 年前MIT
uppy28,92731,0246.06 MB2111 天前MIT
dropzone018,399849 kB11720 天前MIT
ng-file-upload07,780-32810 年前MIT

前端文件上传库深度对比:blueimp-file-upload、dropzone、filepond、fine-uploader、ng-file-upload、react-dropzone 与 uppy

在现代 Web 应用中,文件上传是常见但复杂的交互场景。开发者需要处理拖拽、预览、进度、分片、断点续传、格式校验等需求。本文将从工程实践角度,深入比较七个主流前端文件上传库的技术实现、适用场景和架构取舍。

📦 核心定位与技术栈适配

不同库对框架生态的支持差异显著,直接影响集成成本。

blueimp-file-upload 是一个基于 jQuery 的通用上传组件,不依赖特定框架,适合传统多页应用或 jQuery 项目。

// blueimp-file-upload:jQuery 插件式调用
$('#fileupload').fileupload({
  url: '/upload',
  dataType: 'json',
  done: function (e, data) {
    console.log('Upload complete');
  }
});

dropzone 同样不绑定框架,通过 CSS 类名自动激活拖拽区,适合原生 JavaScript 或轻量级项目。

// dropzone:自动绑定 DOM 元素
Dropzone.options.myDropzone = {
  url: '/upload',
  maxFilesize: 2,
  accept: function(file, done) {
    if (file.name.endsWith('.jpg')) done();
    else done('Only JPG allowed');
  }
};

filepond 提供原生 JavaScript API 和 React/Vue/Svelte 官方插件,强调“文件对象”抽象,适合需要统一处理文件生命周期的场景。

// filepond:创建文件池实例
import * as FilePond from 'filepond';
const pond = FilePond.create(document.querySelector('.filepond'), {
  server: '/upload',
  allowMultiple: true,
  onaddfile: (error, file) => console.log('Added', file)
});

fine-uploader(已停止维护)曾提供模块化架构,但官方 GitHub 仓库已归档,不建议用于新项目。

ng-file-upload 专为 AngularJS(1.x)设计,使用指令语法,在 Angular 2+ 项目中无法直接使用。

// ng-file-upload:AngularJS 指令
// HTML: <div nv-file-drop uploader="uploader">
// JS:
$scope.uploader = new FileUploader({
  url: '/upload',
  autoUpload: true
});

react-dropzone 是 React 专用的 Hook 库,不包含 UI,仅提供拖拽逻辑,需自行实现界面。

// react-dropzone:React Hook 用法
import { useDropzone } from 'react-dropzone';

function MyDropzone() {
  const { getRootProps, getInputProps, acceptedFiles } = useDropzone({
    onDrop: files => console.log('Dropped', files)
  });
  return (
    <div {...getRootProps()}>
      <input {...getInputProps()} />
      <p>拖放文件</p>
    </div>
  );
}

uppy 是模块化上传器,核心库无 UI,通过插件组合功能(如 Dashboard、ProgressBar),支持 React/Vue/Angular 集成。

// uppy:核心 + 插件模式
import Uppy from '@uppy/core';
import Dashboard from '@uppy/dashboard';
import XHRUpload from '@uppy/xhr-upload';

const uppy = new Uppy();
uppy.use(Dashboard, { target: '#uppy' });
uppy.use(XHRUpload, { endpoint: '/upload' });

🖼️ 文件预览与元数据处理

预览能力直接影响用户体验,各库实现方式不同。

blueimp-file-upload 通过 add 回调访问文件对象,需手动创建预览。

$('#fileupload').fileupload({
  add: function (e, data) {
    const file = data.files[0];
    const img = document.createElement('img');
    img.src = URL.createObjectURL(file);
    document.body.appendChild(img);
  }
});

dropzone 自动为图片生成缩略图,通过 thumbnail 事件暴露。

Dropzone.options.myDropzone = {
  thumbnailWidth: 120,
  thumbnailHeight: 120,
  thumbnail: function(file, dataUrl) {
    // dataUrl 是 base64 缩略图
    file.previewElement.querySelector('img').src = dataUrl;
  }
};

filepond 内置智能预览:图片显示缩略图,视频显示第一帧,文档显示图标,并支持自定义预览组件。

FilePond.create(element, {
  // 自动处理预览,无需额外代码
  // 可通过插件扩展预览类型
});

react-dropzone 不提供预览,需开发者用 URL.createObjectURL() 手动实现。

const [previews, setPreviews] = useState([]);
const { getRootProps, getInputProps, acceptedFiles } = useDropzone({
  onDrop: files => {
    const urls = files.map(f => URL.createObjectURL(f));
    setPreviews(urls);
  }
});

uppy 通过 @uppy/thumbnail-generator 插件生成预览,Dashboard UI 自动集成。

import ThumbnailGenerator from '@uppy/thumbnail-generator';
uppy.use(ThumbnailGenerator);
// 预览在 Dashboard 中自动显示

⚙️ 高级功能:分片、断点续传与并发控制

大文件上传需求考验库的底层能力。

blueimp-file-upload 支持分片上传,通过 maxChunkSize 配置分块大小。

$('#fileupload').fileupload({
  maxChunkSize: 1000000, // 1MB 分片
  chunkDone: function (e, data) {
    console.log('Chunk uploaded');
  }
});

dropzone 本身不支持分片,需结合第三方方案(如 tus 协议)。

filepond 通过 @filepond/filepond-plugin-file-encode 等插件支持分片,但需后端配合。

import FilePondPluginFileEncode from '@filepond/filepond-plugin-file-encode';
FilePond.registerPlugin(FilePondPluginFileEncode);
// 分片逻辑需自定义服务器端点

uppy 对分片和断点续传支持最完善,内置 @uppy/tus 插件实现 tus 协议。

import Tus from '@uppy/tus';
uppy.use(Tus, { endpoint: 'https://tusd.tusdemo.net/files/' });
// 自动处理断点续传

react-dropzone 作为基础 Hook,不包含传输逻辑,分片需完全自实现。

🧪 表单集成与验证机制

文件上传常作为表单一部分,验证能力至关重要。

dropzone 提供 acceptedFiles、maxFilesize 等内置验证。

new Dropzone('#my-dropzone', {
  acceptedFiles: 'image/*,.pdf',
  maxFilesize: 5, // MB
  maxFiles: 3
});

filepond 验证规则更细粒度,支持同步/异步校验函数。

FilePond.create(element, {
  acceptedFileTypes: ['image/jpeg', 'image/png'],
  maxFileSize: '5MB',
  maxFiles: 3,
  // 自定义校验
  server: {
    process: (fieldName, file, metadata, load, error, progress, abort) => {
      if (file.size > 5000000) {
        error('文件过大');
        return;
      }
      // 上传逻辑
    }
  }
});

uppy 通过 restrictions 配置基础验证,复杂逻辑需监听 file-added 事件。

const uppy = new Uppy({
  restrictions: {
    maxFileSize: 5000000,
    maxNumberOfFiles: 3,
    allowedFileTypes: ['image/*']
  }
});

react-dropzone 在 accept 属性中指定 MIME 类型,其他验证需在 onDrop 中处理。

useDropzone({
  accept: {
    'image/*': ['.jpeg', '.png']
  },
  onDrop: (accepted, rejected) => {
    if (rejected.length) alert('文件类型不符');
  }
});

🧩 架构灵活性与可扩展性

长期维护性取决于库的扩展模型。

uppy 的插件系统最灵活:核心仅处理文件队列,UI、传输协议、预览等均由插件实现,可按需加载。

filepond 采用插件扩展模式,但 UI 与核心耦合较紧,定制样式需覆盖 CSS。

react-dropzone 无内置 UI,完全由开发者控制渲染,适合需要深度定制的场景。

dropzone 和 blueimp-file-upload 配置项丰富,但修改 UI 需重写模板或覆盖样式。

🚫 已弃用库的明确警示

  • fine-uploader:官方 GitHub 仓库(Widen/fine-uploader)已归档,npm 包标记为 deprecated,绝对不要用于新项目。
  • ng-file-upload:仅支持 AngularJS(1.x),Angular 2+ 项目应使用 ngx-file-drop 等替代品。

💡 选型决策树

根据项目需求快速匹配:

  • 需要完整 UI + 开箱即用 → dropzone(简单场景)或 filepond(高级预览)
  • React 项目 + 自定义 UI → react-dropzone
  • 大文件 + 断点续传 → uppy + tus 插件
  • 传统 jQuery 项目 → blueimp-file-upload
  • AngularJS 1.x 遗留项目 → ng-file-upload(仅限维护)
  • 避免 → fine-uploader(已弃用)

📊 功能对比速查表

功能blueimpdropzonefilepondfine-uploaderng-file-uploadreact-dropzoneuppy
框架无关✅✅✅✅❌ (AngularJS)❌ (React)✅
内置 UI✅✅✅✅✅❌✅ (插件)
拖拽支持✅✅✅✅✅✅✅
图片预览手动自动智能✅✅手动插件
分片上传✅❌插件✅❌❌✅ (tus)
断点续传❌❌❌✅❌❌✅
验证机制基础内置高级内置基础基础内置+事件
维护状态活跃活跃活跃已弃用仅维护活跃活跃

💎 总结

没有“最好”的库,只有“最合适”的选择:

  • 追求零配置快速集成?dropzone 一行 HTML 即可工作。
  • 需要精致的用户体验?filepond 的预览和交互细节值得投入。
  • 构建大型应用需未来-proof?uppy 的插件架构提供长期扩展性。
  • 在React 生态?react-dropzone 给你最大自由度。
  • 切勿在新项目中使用已弃用的 fine-uploader —— 技术债从第一天就开始累积。

最终,评估标准应是:你的团队能否高效实现需求,且未来能轻松维护。

如何选择: react-dropzone vs filepond vs blueimp-file-upload vs fine-uploader vs uppy vs dropzone vs ng-file-upload

  • react-dropzone:

    选择 react-dropzone 如果你在 React 项目中需要完全控制上传 UI 和交互逻辑。它是一个轻量级 Hook 库,不包含任何样式或传输实现,适合需要深度定制或与现有设计系统无缝集成的场景,但所有高级功能(如分片、预览)需自行实现。

  • filepond:

    选择 filepond 如果你追求精致的用户体验和强大的文件处理抽象,且愿意接受其稍高的学习曲线。它提供智能预览(图片、视频、文档)、细粒度验证和插件扩展能力,官方支持 React/Vue/Svelte,适合需要统一文件管理接口的中大型应用。

  • blueimp-file-upload:

    选择 blueimp-file-upload 如果你正在维护基于 jQuery 的传统 Web 应用,需要一个成熟、稳定且不依赖现代框架的上传解决方案。它提供了完整的 UI 和分片上传支持,但需要手动处理文件预览等细节,适合对 jQuery 生态有强依赖的团队。

  • fine-uploader:

    fine-uploader 已被官方弃用(GitHub 仓库归档,npm 包标记 deprecated),绝不应在新项目中使用。如果你在维护遗留系统,应制定迁移计划转向 uppy 或 filepond 等活跃维护的替代品。

  • uppy:

    选择 uppy 如果你需要企业级文件上传能力,包括分片、断点续传(通过 tus 协议)、云存储集成和模块化架构。其插件系统允许按需加载功能,官方提供 React/Vue/Angular 绑定,适合复杂应用或需要未来扩展性的项目,但初始配置比单一功能库更复杂。

  • dropzone:

    选择 dropzone 如果你需要一个零配置、开箱即用的拖拽上传组件,且项目不使用 React/Vue 等现代框架。它自动处理图片缩略图、进度条和基本验证,CSS 类名驱动的设计使其易于集成到任何 HTML 项目中,但缺乏对分片上传和断点续传的原生支持。

  • ng-file-upload:

    选择 ng-file-upload 仅当你在维护 AngularJS(1.x)遗留项目时。它为 AngularJS 提供了指令式 API,但在 Angular 2+ 或其他框架中完全不适用。新 Angular 项目应考虑 ngx-file-drop 等现代替代方案。

react-dropzone的README

react-dropzone logo

react-dropzone

npm Tests coverage Open Collective Backers Open Collective Sponsors Gitpod Contributor Covenant

Simple React hook to create a HTML5-compliant drag'n'drop zone for files.

Documentation and examples at https://react-dropzone.js.org. Source code at https://github.com/react-dropzone/react-dropzone/.

Installation

Install it from npm. react-dropzone ships as ESM and CommonJS with TypeScript types included, and works with any modern bundler (Vite, webpack, Rspack, etc.).

npm install react-dropzone

or:

yarn add react-dropzone

Usage

You can either use the hook:

import React, {useCallback} from "react";
import {useDropzone} from "react-dropzone";

function MyDropzone() {
  const onDrop = useCallback(acceptedFiles => {
    // Do something with the files
  }, []);
  const {getRootProps, getInputProps, isDragActive} = useDropzone({onDrop});

  return (
    <div {...getRootProps()}>
      <input {...getInputProps()} />
      {isDragActive ? <p>Drop the files here ...</p> : <p>Drag 'n' drop some files here, or click to select files</p>}
    </div>
  );
}

Or the wrapper component for the hook:

import React from "react";
import Dropzone from "react-dropzone";

<Dropzone onDrop={acceptedFiles => console.log(acceptedFiles)}>
  {({getRootProps, getInputProps}) => (
    <section>
      <div {...getRootProps()}>
        <input {...getInputProps()} />
        <p>Drag 'n' drop some files here, or click to select files</p>
      </div>
    </section>
  )}
</Dropzone>;

If you want to access file contents you have to use the FileReader API:

import React, {useCallback} from "react";
import {useDropzone} from "react-dropzone";

function MyDropzone() {
  const onDrop = useCallback(acceptedFiles => {
    acceptedFiles.forEach(file => {
      const reader = new FileReader();

      reader.onabort = () => console.log("file reading was aborted");
      reader.onerror = () => console.log("file reading has failed");
      reader.onload = () => {
        // Do whatever you want with the file contents
        const binaryStr = reader.result;
        console.log(binaryStr);
      };
      reader.readAsArrayBuffer(file);
    });
  }, []);
  const {getRootProps, getInputProps} = useDropzone({onDrop});

  return (
    <div {...getRootProps()}>
      <input {...getInputProps()} />
      <p>Drag 'n' drop some files here, or click to select files</p>
    </div>
  );
}

Dropzone Props Getters

The dropzone property getters are just two functions that return objects with properties which you need to use to create the drag 'n' drop zone. The root properties can be applied to whatever element you want, whereas the input properties must be applied to an <input>:

import React from "react";
import {useDropzone} from "react-dropzone";

function MyDropzone() {
  const {getRootProps, getInputProps} = useDropzone();

  return (
    <div {...getRootProps()}>
      <input {...getInputProps()} />
      <p>Drag 'n' drop some files here, or click to select files</p>
    </div>
  );
}

Note that whatever other props you want to add to the element where the props from getRootProps() are set, you should always pass them through that function rather than applying them on the element itself. This is in order to avoid your props being overridden (or overriding the props returned by getRootProps()):

<div
  {...getRootProps({
    onClick: event => console.log(event),
    role: "button",
    "aria-label": "drag and drop area"
    // ...and any other props
  })}
/>

In the example above, the provided {onClick} handler will be invoked before the internal one, therefore, internal callbacks can be prevented by simply using stopPropagation. See Events for more examples.

Important: if you omit rendering an <input> and/or binding the props from getInputProps(), opening a file dialog will not be possible.

Refs

Both getRootProps and getInputProps accept a custom refKey (defaults to ref) as one of the attributes passed down in the parameter.

This can be useful when the element you're trying to apply the props from either one of those fns does not expose a reference to the element, e.g:

import React from "react";
import {useDropzone} from "react-dropzone";
// NOTE: After v4.0.0, styled components exposes a ref using forwardRef,
// therefore, no need for using innerRef as refKey
import styled from "styled-components";

const StyledDiv = styled.div`
  // Some styling here
`;
function Example() {
  const {getRootProps, getInputProps} = useDropzone();
  return (
    <StyledDiv {...getRootProps({refKey: "innerRef"})}>
      <input {...getInputProps()} />
      <p>Drag 'n' drop some files here, or click to select files</p>
    </StyledDiv>
  );
}

If you're working with Material UI v4 and would like to apply the root props on some component that does not expose a ref, use RootRef:

import React from "react";
import {useDropzone} from "react-dropzone";
import RootRef from "@material-ui/core/RootRef";

function PaperDropzone() {
  const {getRootProps, getInputProps} = useDropzone();
  const {ref, ...rootProps} = getRootProps();

  return (
    <RootRef rootRef={ref}>
      <Paper {...rootProps}>
        <input {...getInputProps()} />
        <p>Drag 'n' drop some files here, or click to select files</p>
      </Paper>
    </RootRef>
  );
}

IMPORTANT: do not set the ref prop on the elements where getRootProps()/getInputProps() props are set, instead, get the refs from the hook itself:

import React from "react";
import {useDropzone} from "react-dropzone";

function Refs() {
  const {
    getRootProps,
    getInputProps,
    rootRef, // Ref to the `<div>`
    inputRef // Ref to the `<input>`
  } = useDropzone();
  return (
    <div {...getRootProps()}>
      <input {...getInputProps()} />
      <p>Drag 'n' drop some files here, or click to select files</p>
    </div>
  );
}

If you're using the <Dropzone> component, though, you can set the ref prop on the component itself which will expose the {open} prop that can be used to open the file dialog programmatically:

import React, {createRef} from "react";
import Dropzone from "react-dropzone";

const dropzoneRef = createRef();

<Dropzone ref={dropzoneRef}>
  {({getRootProps, getInputProps}) => (
    <div {...getRootProps()}>
      <input {...getInputProps()} />
      <p>Drag 'n' drop some files here, or click to select files</p>
    </div>
  )}
</Dropzone>;

dropzoneRef.current.open();

Testing

react-dropzone makes some of its drag 'n' drop callbacks asynchronous to enable promise based getFilesFromEvent() functions. In order to test components that use this library, you need to use the react-testing-library:

import React from "react";
import Dropzone from "react-dropzone";
import {act, fireEvent, render} from "@testing-library/react";

test("invoke onDragEnter when dragenter event occurs", async () => {
  const file = new File([JSON.stringify({ping: true})], "ping.json", {type: "application/json"});
  const data = mockData([file]);
  const onDragEnter = jest.fn();

  const ui = (
    <Dropzone onDragEnter={onDragEnter}>
      {({getRootProps, getInputProps}) => (
        <div {...getRootProps()}>
          <input {...getInputProps()} />
        </div>
      )}
    </Dropzone>
  );
  const {container} = render(ui);

  await act(() => fireEvent.dragEnter(container.querySelector("div"), data));
  expect(onDragEnter).toHaveBeenCalled();
});

function mockData(files) {
  return {
    dataTransfer: {
      files,
      items: files.map(file => ({
        kind: "file",
        type: file.type,
        getAsFile: () => file
      })),
      types: ["Files"]
    }
  };
}

NOTE: using Enzyme for testing is not supported at the moment, see #2011.

More examples for this can be found in react-dropzone's own test suites.

Caveats

Required React Version

React 18 or above is required because we use hooks (the lib itself is a hook).

File Paths

Files returned by the hook or passed as arg to the onDrop cb won't have the properties path or fullPath. For more inf check this SO question and this issue.

Tauri (and other native webviews)

Inside a Tauri webview, OS file drops are intercepted by the native window layer before the webview sees them, so the browser's drag 'n' drop events never fire and the dropzone appears to ignore drops. Disable Tauri's native handling to let react-dropzone work unchanged: set dragDropEnabled: false (Tauri v2) or fileDropEnabled: false (Tauri v1) in tauri.conf.json. If you instead need the absolute paths of dropped files, keep native handling on and drive your own state from Tauri's drag-drop event. See the Using with Tauri guide and #1316.

Not a File Uploader

This lib is not a file uploader; as such, it does not process files or provide any way to make HTTP requests to some server; if you're looking for that, checkout filepond or uppy.io.

Paste to Upload

Pasting files into the dropzone is supported out of the box: when the dropzone (or a focused child, e.g. a <textarea>) has focus and the user pastes files with Ctrl/Cmd+V - for instance a screenshot copied to the clipboard - those files run through the same accept, size and validator checks and the same onDrop/onDropAccepted/onDropRejected callbacks as a drop. Pastes that carry no files (plain text and the like) are ignored and left untouched, so pasting into inputs keeps working.

The paste is only received when the dropzone has focus, so pair it with autoFocus or let the user click/tab into it first. To disable paste handling, set noPaste:

import React from "react";
import {useDropzone} from "react-dropzone";

function MyDropzone() {
  const {getRootProps, getInputProps} = useDropzone({noPaste: true});

  return (
    <div {...getRootProps()}>
      <input {...getInputProps()} />
      <p>Drag 'n' drop or click - pasting is disabled</p>
    </div>
  );
}

Using <label> as Root

If you use <label> as the root element, the file dialog will be opened twice; see #1107 and #1432 why. A <label> natively forwards clicks to the <input> it wraps, so the dialog opens once from that and once from our own click handler. To avoid this, use noClick:

import React, {useCallback} from "react";
import {useDropzone} from "react-dropzone";

function MyDropzone() {
  const {getRootProps, getInputProps} = useDropzone({noClick: true});

  return (
    <label {...getRootProps()}>
      <input {...getInputProps()} />
    </label>
  );
}

Using open() on Click

If you bind a click event on an inner element and use open(), it will trigger a click on the root element too, resulting in the file dialog opening twice. To prevent this, use the noClick on the root:

import React, {useCallback} from "react";
import {useDropzone} from "react-dropzone";

function MyDropzone() {
  const {getRootProps, getInputProps, open} = useDropzone({noClick: true});

  return (
    <div {...getRootProps()}>
      <input {...getInputProps()} />
      <button type="button" onClick={open}>
        Open
      </button>
    </div>
  );
}

Changing Props Before open()

Browsers only let you open the file dialog from within a short window after a user gesture (a "transient user activation"). Because of this, open() must run synchronously in the same event that the user triggered - it cannot wait for React to re-render.

So if you update a prop (e.g. accept) and call open() in the same handler, open() runs against the previous render, before the new prop is applied. Some browsers (notably Safari) may also drop the file dialog entirely, so it appears to require a second click. See #1188.

// 🚫 open() runs with the old `accept` - and may not open at all on Safari
function MyDropzone() {
  const [accept, setAccept] = useState({"image/*": []});
  const {getRootProps, getInputProps, open} = useDropzone({accept, noClick: true});

  const pickPdfs = () => {
    setAccept({"application/pdf": []}); // applied on the next render
    open(); // still uses {"image/*": []}
  };

  return (
    <div {...getRootProps()}>
      <input {...getInputProps()} />
      <button type="button" onClick={pickPdfs}>
        Pick PDFs
      </button>
    </div>
  );
}

Instead, render one dropzone per set of props (each with its own open) and call the right one, so no state change has to land before opening:

function MyDropzone() {
  const images = useDropzone({accept: {"image/*": []}, noClick: true});
  const pdfs = useDropzone({accept: {"application/pdf": []}, noClick: true});

  return (
    <div {...images.getRootProps()}>
      <input {...images.getInputProps()} />
      <input {...pdfs.getInputProps()} />
      <button type="button" onClick={images.open}>
        Pick images
      </button>
      <button type="button" onClick={pdfs.open}>
        Pick PDFs
      </button>
    </div>
  );
}

Camera Option Missing on Android

On Android 13+, Chrome and Edge open the native Android photo picker whenever a file input's accept attribute resolves to only image and/or video types (e.g. accept={{"image/*": []}}). That picker only lists the gallery and files - it has no "take a photo"/camera action - so the camera appears to be missing. See #1417.

This is browser/OS behavior, not something the lib controls, and it's unrelated to how the <input> is styled: a plain <input type="file"> with no accept (or one that also allows a non-media type) falls back to the older chooser that does include the Camera app.

If you need the camera, use the capture attribute, which you can pass straight through getInputProps():

// Camera only (no gallery/files)
<input {...getInputProps({capture: "environment"})} />

To offer both the camera and the gallery, render one dropzone per input (each with its own open) and let the user pick which one to trigger:

import React from "react";
import {useDropzone} from "react-dropzone";

function MyDropzone({onDrop}) {
  const gallery = useDropzone({accept: {"image/*": []}, noClick: true, onDrop});
  const camera = useDropzone({accept: {"image/*": []}, noClick: true, onDrop});

  return (
    <>
      <button type="button" onClick={gallery.open}>
        Choose from gallery
      </button>
      <input {...gallery.getInputProps()} />

      <button type="button" onClick={camera.open}>
        Take a photo
      </button>
      <input {...camera.getInputProps({capture: "environment"})} />
    </>
  );
}

File Dialog Cancel Callback

The onFileDialogCancel() cb is unstable in most browsers, meaning, there's a good chance of it being triggered even though you have selected files.

We rely on using a timeout of 300ms after the window is focused (the window onfocus event is triggered when the file select dialog is closed) to check if any files were selected and trigger onFileDialogCancel if none were selected.

As one can imagine, this doesn't really work if there's a lot of files or large files as by the time we trigger the check, the browser is still processing the files and no onchange events are triggered yet on the input. Check #1031 for more info.

Fortunately, there's the File System Access API, which is currently a working draft and some browsers support it (see browser compatibility), that provides a reliable way to prompt the user for file selection and capture cancellation.

Also keep in mind that the FS access API can only be used in secure contexts.

NOTE You can enable using the FS access API with the useFsAccessApi property: useDropzone({useFsAccessApi: true}).

File System Access API

When setting useFsAccessApi to true, you're switching to the File System API (see the file system access RFC).

What this essentially does is that it will use the showOpenFilePicker method to open the file picker window so that the user can select files.

In contrast, the traditional way (when the useFsAccessApi is not set to true or not specified) uses an <input type="file"> (see docs) on which a click event is triggered.

With the use of the file system access API enabled, there's a couple of caveats to keep in mind:

  1. The users will not be able to select directories
  2. It requires the app to run in a secure context
  3. In Electron, the path may not be set (see #1249)
  4. Some browsers/configurations block showOpenFilePicker() and reject with a NotAllowedError (e.g. Microsoft Edge for Business or other restrictive enterprise/security policies). When this happens we automatically fall back to the native <input> so the file dialog still opens, provided you render one via getInputProps() (see #1429)

Supported Browsers

We use browserslist config to state the browser support for this lib, so check it out on browserslist.dev.

Need image editing?

React Dropzone integrates perfectly with Pintura Image Editor, creating a modern image editing experience. Pintura supports crop aspect ratios, resizing, rotating, cropping, annotating, filtering, and much more.

Checkout the Pintura integration example.

Support

Backers

Support us with a monthly donation and help us continue our activities. [Become a backer]

Sponsors

Become a sponsor and get your logo on our README on Github with a link to your site. [Become a sponsor]

Hosting

react-dropzone.js.org hosting provided by netlify.

Contribute

Checkout the organization CONTRIBUTING.md.

License

MIT