ファイルアップロードUIライブラリ比較
3つのアプローチでドラッグ&ドロップ対応のファイルアップロードUIを実装し、機能とスタイルを比較
共通の動作仕様
各デモではドラッグ&ドロップまたはクリックでファイルを選択できます。アップロードはシミュレーション(擬似処理)で、プログレスバーが進行します。
受け付けるファイルの想定は3方式で共通ですが、それを実際に検証するかどうかは方式ごとに異なります。ライブラリの素の挙動を比べられるよう、各デモは既定の検証機能をそのまま使っています。
{
"対応ファイル(想定)": ["画像 (PNG/JPG/GIF/WebP)", "PDF", "テキスト", "CSV"],
"最大サイズ(想定)": "10MB",
"複数ファイル": true,
"アップロード": "シミュレーション(デモ用擬似プログレス)",
"検証の実態": {
"react-dropzone": "種別・サイズとも検証し、弾いたファイルを日本語のエラーで表示",
"FilePond": "種別のみ検証(プラグイン)。サイズは未検証で10MB超も通る",
"カスタム実装": "検証なし。ドロップされたファイルをすべて受理する"
}
}1. react-dropzone
軽量なドラッグ&ドロップフック — UIは自由にカスタマイズ可能なヘッドレス設計
ファイルをドラッグ&ドロップ、またはクリックして選択
画像(PNG・JPG・GIF・WebP)/PDF/テキスト(TXT・CSV) 最大10MB
特徴
- • React Hooksベースでシンプルに統合
- • ファイル種別・サイズのバリデーション内蔵
- • ドロップゾーンのUI/スタイルは完全にカスタム
- • バンドルサイズが非常に小さい(~8KB gzipped)
インストール
npm install react-dropzone'use client';
import { useDropzone, ErrorCode } from 'react-dropzone';
import type { FileError, FileRejection } from 'react-dropzone';
import { useState, useCallback } from 'react';
type Rejected = { id: string; name: string; size: number; errors: string[] };
// 既定のエラー文言は英語(例: 'File is larger than 10 MB')。
// getErrorMessage はエラー1件ごとに呼ばれ、返した文字列が message を置き換える。
// 変更しないコードは error.message をそのまま返すのが公式の作法
const getErrorMessage = (error: FileError, file: File) => {
switch (error.code) {
case ErrorCode.FileInvalidType:
return '対応していない形式です(PNG・JPG・GIF・WebP・PDF・TXT・CSV のみ)';
case ErrorCode.FileTooLarge:
return `サイズが上限の10MBを超えています(${(file.size / 1024 / 1024).toFixed(1)} MB)`;
default:
return error.message;
}
};
export function ReactDropzoneDemo() {
const [files, setFiles] = useState<File[]>([]);
const [rejected, setRejected] = useState<Rejected[]>([]);
// onDrop の第2引数で拒否ファイルを受け取り、自前の state に累積する。
// useDropzone の戻り値にも fileRejections があるが、そちらは直近ドロップ分のみで毎回置き換わる
const onDrop = useCallback((accepted: File[], rejections: FileRejection[]) => {
setFiles(prev => [...prev, ...accepted]);
setRejected(prev => [
...prev,
...rejections.map(r => ({
id: `${Date.now()}-${Math.random().toString(36).slice(2)}`,
name: r.file.name,
size: r.file.size,
// 1ファイルに複数のエラーが付くことがある(種別 + サイズなど)
errors: r.errors.map(e => e.message),
})),
]);
}, []);
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop,
accept: {
'image/*': ['.png', '.jpg', '.jpeg', '.gif', '.webp'],
'application/pdf': ['.pdf'],
'text/*': ['.txt', '.csv'],
},
maxSize: 10 * 1024 * 1024,
// v19.2.0 以降、ペーストでのアップロードが既定で有効。デモでは無効化する
noPaste: true,
getErrorMessage,
});
return (
<div>
<div
{...getRootProps()}
className={`border-2 border-dashed rounded-lg p-8 text-center cursor-pointer transition-colors ${
isDragActive ? 'border-blue-500 bg-blue-50' : 'border-gray-300 hover:border-gray-400'
}`}
>
<input {...getInputProps()} />
<p className="text-gray-500 text-sm">
{isDragActive ? 'ここにドロップ' : 'クリックまたはドラッグ&ドロップでファイルを選択'}
</p>
<p className="text-gray-400 text-xs mt-1">
画像(PNG・JPG・GIF・WebP)/PDF/テキスト(TXT・CSV) 最大10MB
</p>
</div>
{/* 拒否ファイル。ドロップゾーンと受理リストの間に置き、直前の操作結果を先に見せる */}
{rejected.length > 0 && (
<div className="mt-4 space-y-2">
<div className="flex items-center justify-between">
<p className="text-sm font-medium text-red-700">
受け付けられなかったファイル({rejected.length}件)
</p>
<button onClick={() => setRejected([])} className="text-xs text-red-500 hover:text-red-700">
すべて削除
</button>
</div>
{rejected.map(r => (
<div key={r.id} className="border border-red-200 bg-red-50 rounded-lg p-3 flex items-start gap-3">
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-red-700 truncate">{r.name}</p>
<p className="text-xs text-red-700">{(r.size / 1024).toFixed(1)} KB</p>
<ul className="mt-1 space-y-0.5">
{r.errors.map((message, i) => (
<li key={i} className="text-xs text-red-700">{message}</li>
))}
</ul>
</div>
<button
onClick={() => setRejected(prev => prev.filter(x => x.id !== r.id))}
className="text-red-400 hover:text-red-600 text-sm"
>
×
</button>
</div>
))}
</div>
)}
{files.length > 0 && (
<ul className="mt-4 space-y-2">
{files.map((f, i) => (
<li key={i} className="flex items-center gap-2 text-sm text-gray-700">
<span>📎</span>
<span>{f.name}</span>
<span className="text-gray-400">({(f.size / 1024).toFixed(1)} KB)</span>
</li>
))}
</ul>
)}
</div>
);
}AIプロンプトテンプレート
React + Tailwind CSSで、react-dropzoneを使ったドラッグ&ドロップファイルアップロードUIを実装してください。 - 使用ライブラリ: react-dropzone(useDropzone フック) - useDropzone の onDrop、accept、maxSize プロパティでファイル受け取りとバリデーションを設定すること - noPaste: true でペーストによるアップロードを無効にすること(react-dropzone は v19.2.0 以降、既定でペーストされたファイルを受け付けるため) - getRootProps/getInputProps をドロップゾーンのdivとinputに適用すること - isDragActive でドラッグ中のスタイルを切り替えること - 各ファイルのアップロード状態(pending/uploading/complete)とプログレスをstateで管理すること - ドラッグ&ドロップとクリックによるファイル選択の両方に対応すること - ファイル削除ボタンとアップロード済みファイルのリスト表示を実装すること - onDrop の第2引数(FileRejection[])で拒否ファイルを受け取り、専用のstateに累積して一覧表示すること(useDropzone の戻り値 fileRejections は直近ドロップ分のみで毎回置き換わるため、受理リストと挙動が非対称になる) - getErrorMessage で file-invalid-type / file-too-large のエラー文言を日本語化すること(既定の文言は英語。ErrorCode で分岐し、変更しないコードは error.message をそのまま返す) - 拒否ファイルの表示は受理ファイルと対称にし、個別削除・すべて削除ができるようにすること(1ファイルに複数のエラーが付く場合があるので errors は配列として描画すること)
このプロンプトはあくまでたたき台です。AIの回答はモデルやバージョン、会話の文脈によって毎回異なります。意図通りに動かない場合は、条件を追記・修正してお使いください。
2. FilePond (react-filepond)
美しいUI内蔵のファイルアップロード — プログレス・プレビュー・アニメーション付き
最大5ファイルまで。画像、PDF、テキストファイルに対応
特徴
- • 洗練されたUIとアニメーションが標準搭載
- • 画像プレビュー・ファイル検証プラグインが豊富
- • アップロード中のプログレス表示が美しい
- • ドラッグ&ドロップ・ペースト・ブラウズに対応
インストール
npm install filepond react-filepond filepond-plugin-file-validate-type filepond-plugin-image-preview'use client';
import { FilePond, registerPlugin } from 'react-filepond';
import 'filepond/dist/filepond.min.css';
import FilePondPluginImagePreview from 'filepond-plugin-image-preview';
import 'filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css';
import { useState } from 'react';
registerPlugin(FilePondPluginImagePreview);
export function FilePondDemo() {
const [files, setFiles] = useState<File[]>([]);
return (
<FilePond
files={files}
onupdatefiles={items => setFiles(items.map(i => i.file as File))}
allowMultiple={true}
labelIdle='ファイルをドラッグ&ドロップ、または <span class="filepond--label-action">ブラウズ</span>'
/>
);
}AIプロンプトテンプレート
React + Tailwind CSSで、FilePond(react-filepond)を使ったファイルアップロードUIを実装してください。
- 使用ライブラリ: filepond、react-filepond、filepond-plugin-file-validate-type、filepond-plugin-image-preview
- FilePond コンポーネントと registerPlugin でプラグインを登録すること
- allowMultiple={true}、maxFiles={5}、acceptedFileTypes でファイル制限を設定すること
- server プロパティの process 関数でアップロードのシミュレーション(擬似プログレス)を実装すること
- labelIdle、labelFileProcessing など日本語ラベルを設定すること
- credits={false} でFilePondクレジット表示を非表示にすることこのプロンプトはあくまでたたき台です。AIの回答はモデルやバージョン、会話の文脈によって毎回異なります。意図通りに動かない場合は、条件を追記・修正してお使いください。
3. カスタム実装(React + Tailwind CSS)
ライブラリ不要の完全カスタム実装 — 全体プログレスバー・リトライ・ファイル一覧を自作
ドラッグ&ドロップでアップロード
またはクリックしてファイルを選択
特徴
- • 外部ライブラリ不要で依存関係が最小限
- • 全体のプログレス表示・個別のプログレス表示
- • ドラッグオーバー時のオーバーレイ演出
- • リトライ機能・ファイル一覧の管理
インストール
追加パッケージ不要(React + Tailwind CSS のみ)'use client';
import { useState, useCallback } from 'react';
export function CustomUploadDemo() {
const [files, setFiles] = useState<{ name: string; size: number }[]>([]);
const [isDragging, setIsDragging] = useState(false);
const handleDrop = useCallback((e: React.DragEvent) => {
e.preventDefault();
setIsDragging(false);
const dropped = Array.from(e.dataTransfer.files);
setFiles(prev => [...prev, ...dropped.map(f => ({ name: f.name, size: f.size }))]);
}, []);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const selected = Array.from(e.target.files ?? []);
setFiles(prev => [...prev, ...selected.map(f => ({ name: f.name, size: f.size }))]);
};
return (
<div>
<label
onDragOver={e => { e.preventDefault(); setIsDragging(true); }}
onDragLeave={() => setIsDragging(false)}
onDrop={handleDrop}
className={`block border-2 border-dashed rounded-lg p-8 text-center cursor-pointer transition-colors ${
isDragging ? 'border-emerald-500 bg-emerald-50' : 'border-gray-300 hover:border-gray-400'
}`}
>
<input type="file" multiple className="hidden" onChange={handleChange} />
<p className="text-gray-500 text-sm">クリックまたはドラッグ&ドロップ</p>
</label>
{files.length > 0 && (
<ul className="mt-4 space-y-2">
{files.map((f, i) => (
<li key={i} className="flex items-center justify-between text-sm p-2 bg-gray-50 rounded">
<span>{f.name}</span>
<span className="text-gray-400">{(f.size / 1024).toFixed(1)} KB</span>
</li>
))}
</ul>
)}
</div>
);
}AIプロンプトテンプレート
React + Tailwind CSSで、ライブラリ不要のカスタムファイルアップロードUIを実装してください。 - 使用ライブラリ: React + Tailwind CSS のみ(外部アップロードライブラリなし) - onDragEnter/onDragLeave/onDrop イベントハンドラーを自作してドロップゾーンを実装すること - dragCountRef(useRef)で dragEnter/dragLeave のカウントを管理し、子要素への移動で非表示にならないよう制御すること - ファイルごとのプログレスバーと全ファイルの全体プログレスバーの2段階表示を実装すること - ドラッグオーバー時にオーバーレイを表示すること - ファイル削除ボタンとリトライ機能を実装すること - ファイル種別に応じた絵文字アイコンとファイルサイズの書式化表示を実装すること
このプロンプトはあくまでたたき台です。AIの回答はモデルやバージョン、会話の文脈によって毎回異なります。意図通りに動かない場合は、条件を追記・修正してお使いください。
ライブラリ比較表
| 項目 | react-dropzone | FilePond | カスタム実装 |
|---|---|---|---|
| ドラッグ&ドロップ | 内蔵 | 内蔵 | 自作 |
| プログレス表示 | 自作 | 内蔵(美麗) | 自作 |
| 画像プレビュー | 自作 | プラグイン提供 | 自作 |
| ファイル検証 | 内蔵(種別・サイズ。本デモはエラーを日本語で表示) | プラグイン提供(本デモは種別のみ・文言は英語) | 自作(本デモは未実装) |
| UIデザイン | ヘッドレス(自作) | 内蔵(美麗) | 完全カスタム |
| バンドルサイズ | 小さい(~8KB) | 中程度(~40KB) | 最小 |
| 学習コスト | 低 | 低〜中 | 中〜高(要件次第) |
| おすすめ用途 | 独自UIのアップロード | 美しいUIが必要な場合 | シンプルな要件 |
まとめ
react-dropzone — ドラッグ&ドロップのロジックだけを提供するヘッドレス設計。独自のデザインシステムに合わせたアップロードUIを構築したい場合に最適。
FilePond — 洗練されたUIとアニメーションが標準搭載。画像プレビューやプログレス表示など、美しいアップロード体験を素早く実装したい場合におすすめ。
カスタム実装 — 外部ライブラリに依存せず、全体プログレスやリトライ機能など細かいUX制御をしたい場合に最適。
ファイルアップロードUIは、ローカルファイルをドラッグ&ドロップまたはクリックで選択してアップロードするインターフェース。
プロフィール画像の変更・ドキュメント提出・画像投稿SNS・ファイル共有サービスなど、ユーザーがファイルをサーバーに送信する場面で使われる。
- •ドラッグ&ドロップ型
- •クリック選択型
- •プレビュー付き型(画像)
- •複数ファイル対応型
- •プログレスバー付き型