npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

direct-drop-area

v2.0.4

Published

A lightweight, feature-rich drag-and-drop file upload component for React. Supports multiple files, validation, previews, paste, and more.

Readme

📁 Direct Drop Area

A lightweight, feature-rich drag-and-drop file upload component for React.

No dependencies. No bloat. Just works.

npm version bundle size license



✨ Features

  • 🎯 Drag & Drop - Drop files anywhere on the zone
  • 🖱️ Click to Upload - Opens native file picker
  • ⌨️ Keyboard Accessible - Full keyboard navigation support
  • 📋 Paste Support - Paste images directly from clipboard
  • 🖼️ Image Previews - Auto-generates preview URLs for images
  • Validation - File type, size, and custom validation
  • 📁 Multiple Files - Support for single or multiple file selection
  • 📂 Directory Upload - Upload entire folders
  • 🔄 Browser URL Drop - Drag images directly from other websites
  • 🪶 Lightweight - Zero dependencies, ~4KB gzipped
  • 📦 TypeScript - Full type definitions included
  • 🌐 SSR Safe - Works with Next.js, Gatsby, etc.

📦 Installation

# npm
npm install direct-drop-area

# yarn
yarn add direct-drop-area

# pnpm
pnpm add direct-drop-area

🚀 Quick Start

Basic Usage

import DirectDropArea from "direct-drop-area";

function App() {
  return (
    <DirectDropArea
      onDrop={(file) => {
        console.log("File dropped:", file);
      }}
    >
      {({ isDraggedOver }) => (
        <div
          style={{
            padding: "40px",
            border: `2px dashed ${isDraggedOver ? "green" : "gray"}`,
            background: isDraggedOver ? "#e8f5e9" : "white",
            textAlign: "center",
          }}
        >
          {isDraggedOver
            ? "Drop it here!"
            : "Drag file here or click to upload"}
        </div>
      )}
    </DirectDropArea>
  );
}

Image Upload with Preview

import { useState } from "react";
import DirectDropArea from "direct-drop-area";

function ImageUploader() {
  const [preview, setPreview] = useState(null);

  return (
    <DirectDropArea
      accept="image/*"
      maxSize={5 * 1024 * 1024} // 5MB
      onDrop={(file) => {
        setPreview(file.preview);
        console.log("Uploaded:", file.name, file.sizeFormatted);
      }}
      onDropRejected={(rejected) => {
        alert(rejected[0].errors[0].message);
      }}
    >
      {({ isDraggedOver, isProcessing }) => (
        <div
          style={{
            width: "300px",
            height: "200px",
            border: `2px dashed ${isDraggedOver ? "#4caf50" : "#ccc"}`,
            borderRadius: "12px",
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
            cursor: "pointer",
            background: isDraggedOver ? "#f0fff0" : "#fafafa",
          }}
        >
          {isProcessing ? (
            <span>Processing...</span>
          ) : preview ? (
            <img
              src={preview}
              alt="Preview"
              style={{ maxWidth: "100%", maxHeight: "100%" }}
            />
          ) : (
            <span>📷 Drop image or click to upload</span>
          )}
        </div>
      )}
    </DirectDropArea>
  );
}

Multiple Files with Validation

import DirectDropArea from "direct-drop-area";

function MultiUploader() {
  return (
    <DirectDropArea
      multiple
      maxFiles={5}
      accept=".pdf,.doc,.docx"
      maxSize={10 * 1024 * 1024} // 10MB per file
      onDropAccepted={(files) => {
        files.forEach((f) => {
          console.log(`✅ ${f.name} (${f.sizeFormatted})`);
        });
      }}
      onDropRejected={(rejected) => {
        rejected.forEach(({ file, errors }) => {
          console.log(`❌ ${file.name}: ${errors[0].message}`);
        });
      }}
    >
      {({ isDraggedOver }) => (
        <div className={`dropzone ${isDraggedOver ? "active" : ""}`}>
          Drop up to 5 documents here (PDF, DOC)
        </div>
      )}
    </DirectDropArea>
  );
}

Read File Contents

<DirectDropArea
  accept=".json,.txt"
  readAs="text"
  onDrop={(file, content) => {
    console.log("File name:", file.name);
    console.log("File content:", file.content);

    if (file.name.endsWith(".json")) {
      const data = JSON.parse(file.content);
      console.log("Parsed JSON:", data);
    }
  }}
>
  {({ isDraggedOver }) => <div>Drop a JSON or text file</div>}
</DirectDropArea>

Custom Styled Dropzone

import DirectDropArea from "direct-drop-area";
import "./styles.css";

function StyledDropzone() {
  return (
    <DirectDropArea accept="image/*" onDrop={(file) => console.log(file)}>
      {({ isDraggedOver, isDraggedOverDocument, isProcessing, open }) => (
        <div
          className={`
          dropzone 
          ${isDraggedOver ? "dropzone--active" : ""} 
          ${isDraggedOverDocument ? "dropzone--document-drag" : ""}
        `}
        >
          <div className="dropzone__icon">📁</div>
          <div className="dropzone__text">
            {isProcessing ? (
              "Processing..."
            ) : isDraggedOver ? (
              "Release to upload!"
            ) : (
              <>
                Drag & drop files here, or{" "}
                <button onClick={open} className="dropzone__button">
                  browse
                </button>
              </>
            )}
          </div>
          <div className="dropzone__hint">
            Supports: JPG, PNG, GIF up to 10MB
          </div>
        </div>
      )}
    </DirectDropArea>
  );
}
/* styles.css */
.dropzone {
  padding: 40px;
  border: 2px dashed #d0d0d0;
  border-radius: 16px;
  text-align: center;
  transition: all 0.2s ease;
  cursor: pointer;
}

.dropzone:hover {
  border-color: #2196f3;
  background: #f3f9ff;
}

.dropzone--active {
  border-color: #4caf50;
  background: #f0fff0;
  transform: scale(1.02);
}

.dropzone--document-drag {
  border-color: #ff9800;
}

.dropzone__icon {
  font-size: 48px;
  margin-bottom: 16px;
}

.dropzone__text {
  font-size: 18px;
  color: #333;
}

.dropzone__hint {
  font-size: 14px;
  color: #888;
  margin-top: 8px;
}

.dropzone__button {
  color: #2196f3;
  background: none;
  border: none;
  cursor: pointer;
  text-decoration: underline;
  font-size: inherit;
}

📖 API Reference

Props

| Prop | Type | Default | Description | | ---------------- | ----------------------- | --------- | -------------------------------------------------------- | | children | function \| ReactNode | required | Render prop function receiving state, or static children | | onDrop | function | - | Called with processed file(s) and content(s) | | onDropAccepted | function | - | Called only with valid files | | onDropRejected | function | - | Called with rejected files and errors | | onError | function | - | Called on processing errors | | accept | string | null | Accepted file types (e.g., "image/*", ".pdf") | | multiple | boolean | false | Allow multiple files | | directory | boolean | false | Allow folder upload | | maxFiles | number | null | Max files (with multiple: true) | | maxSize | number | null | Max file size in bytes | | minSize | number | null | Min file size in bytes | | validate | function | null | Custom validation function | | disabled | boolean | false | Disable the component | | noClick | boolean | false | Disable click to open dialog | | noDrag | boolean | false | Disable drag and drop | | noKeyboard | boolean | false | Disable keyboard interaction | | noPaste | boolean | false | Disable paste support | | readAs | string | null | Read files as 'text', 'dataURL', or 'arrayBuffer' | | encoding | string | 'UTF-8' | Encoding for text reading |

Render Prop State

The render function receives an object with:

{
  isDraggedOver: boolean      // File is dragged over the drop zone
  isDraggedOverDocument: boolean  // File is dragged anywhere on the page
  isProcessing: boolean       // Files are being processed
  isDisabled: boolean         // Component is disabled
  open: () => void            // Programmatically open file dialog
}

File Object

Each processed file includes:

{
  file: File                  // Original File object
  id: string                  // Unique identifier
  name: string                // File name
  size: number                // Size in bytes
  type: string                // MIME type
  sizeFormatted: string       // Human readable size (e.g., "1.5 MB")
  content?: string | ArrayBuffer  // File content (if readAs specified)
  preview?: string            // Data URL for images
}

Rejected File Object

{
  file: File
  errors: Array<{
    code: 'FILE_TYPE_INVALID' | 'FILE_TOO_LARGE' | 'FILE_TOO_SMALL' | 'TOO_MANY_FILES' | 'CUSTOM_VALIDATION'
    message: string
  }>
}

🛠️ Utility Functions

The package exports useful utility functions:

import {
  formatFileSize,
  readAsText,
  readAsDataURL,
  openFileDialog,
  isFileTypeValid,
} from "direct-drop-area";

// Format bytes to human readable
formatFileSize(1536000); // "1.5 MB"

// Read file contents
const text = await readAsText(file);
const dataURL = await readAsDataURL(file);

// Open file dialog programmatically
const file = await openFileDialog({ accept: "image/*" });
const files = await openFileDialog({ multiple: true });

// Validate file type
isFileTypeValid(file, "image/*"); // true/false

🎨 Styling Tips

Using isDraggedOverDocument

Show a full-page overlay when files are dragged anywhere on the page:

function App() {
  return (
    <>
      <DirectDropArea onDrop={handleDrop}>
        {({ isDraggedOver, isDraggedOverDocument }) => (
          <>
            {isDraggedOverDocument && (
              <div className="full-page-overlay">Drop anywhere to upload</div>
            )}
            <div className={`dropzone ${isDraggedOver ? "active" : ""}`}>
              Your drop zone content
            </div>
          </>
        )}
      </DirectDropArea>
    </>
  );
}

Disable During Upload

const [isUploading, setIsUploading] = useState(false)

<DirectDropArea
  disabled={isUploading}
  onDrop={async (file) => {
    setIsUploading(true)
    await uploadToServer(file)
    setIsUploading(false)
  }}
>
  {({ isDisabled }) => (
    <div style={{ opacity: isDisabled ? 0.5 : 1 }}>
      {isDisabled ? 'Uploading...' : 'Drop files here'}
    </div>
  )}
</DirectDropArea>

❓ FAQ

How do I upload to a server?

<DirectDropArea
  onDrop={async (file) => {
    const formData = new FormData()
    formData.append('file', file.file)

    const response = await fetch('/api/upload', {
      method: 'POST',
      body: formData
    })

    const result = await response.json()
    console.log('Uploaded:', result)
  }}
>

Can I use it without the render prop?

Yes! Just pass children directly:

<DirectDropArea onDrop={handleDrop}>
  <div className="my-dropzone">Drop files here</div>
</DirectDropArea>

But you won't get access to isDraggedOver state this way.

Does it work with Next.js / SSR?

Yes! The component checks for browser environment and won't break during server-side rendering.

How do I clear the preview?

The component is stateless - manage previews in your own state:

const [preview, setPreview] = useState(null)

<DirectDropArea onDrop={(f) => setPreview(f.preview)}>
  {() => (
    <>
      {preview && (
        <>
          <img src={preview} />
          <button onClick={() => setPreview(null)}>Remove</button>
        </>
      )}
    </>
  )}
</DirectDropArea>

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

📄 License

MIT © Shreenath Chakinala


💖 Support

If this package helped you, consider giving it a ⭐ on GitHub!

Found a bug? Open an issue