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

ultra-image-uploader

v4.0.0

Published

A modern, shadcn/ui-inspired React image upload component with beautiful themes, drag-and-drop, and smooth animations

Readme

Ultra Image Uploader

A modern, production-ready React image upload component with shadcn/ui-inspired design, beautiful themes, and smooth animations.

npm version TypeScript License: MIT

shadcn/ui Design5 ThemesImgBB & CloudinaryCustomizable


Features

  • Modern UI - Clean, minimal shadcn/ui-inspired design
  • 5 Built-in Themes - Nature, Modern, Fresh, Dark (gradient), Ocean (blue gradient)
  • Drag & Drop - Beautiful drag-and-drop with smooth animations
  • Live Previews - Responsive grid with image thumbnails
  • Progress Tracking - Real-time upload progress with visual feedback
  • Remove Images - Easy removal with trash icon in both create and update modes
  • Error Handling - Built-in API key validation and error display with dismissible alerts
  • Custom Themes - Create your own theme with custom colors
  • Multiple Providers - ImgBB & Cloudinary support
  • File Validation - Size, type, and count validation
  • Accessible - Keyboard navigation and ARIA support
  • Auto Import - Works with VS Code, WebStorm, and all editors
  • Customization API - Border radius, preview size (xs to 2xl), show/hide elements
  • Custom Text Labels - Fully customizable text labels for internationalization
  • Custom Upload Button - Use your own button component with optional drag-and-drop area

Installation

# npm
npm install ultra-image-uploader

# yarn
yarn add ultra-image-uploader

# pnpm
pnpm add ultra-image-uploader

# bun
bun add ultra-image-uploader

Quick Start

import { ImageUploader } from "ultra-image-uploader";
import { useState } from "react";

function App() {
  const [images, setImages] = useState<File[]>([]);

  return <ImageUploader images={images} setImages={setImages} multiple />;
}

Props

| Prop | Type | Default | Description | | ----------------------- | ------------------------------------------------------ | --------------------------- | ------------------------------- | | Core | | images | File[] | Required | Selected image files | | setImages | (files: File[]) => void | Required | Update images state | | Text Labels | | textLabels | ImageUploaderTextLabels | undefined | Custom text labels (i18n) | | Mode | | mode | 'add' \| 'update' | 'add' | Upload mode | | defaultImages | string[] | [] | Default images (update mode) | | File Constraints | | multiple | boolean | true | Allow multiple files | | maxSize | number | 52428800 | Max file size (50MB) | | allowedTypes | string[] | Image types | Allowed MIME types | | maxImages | number | 20 | Maximum images allowed | | Upload | | uploadConfig | { provider, config } | undefined | Upload configuration | | autoUpload | boolean | false | Auto-upload on selection | | onUploadComplete | (urls: string[]) => void | undefined | Success callback | | onUploadError | (error: Error) => void | undefined | Error callback | | Theme & Styling | | theme | 'nature' \| 'modern' \| 'fresh' \| 'dark' \| 'ocean' | 'nature' | Built-in theme | | customTheme | Theme | undefined | Custom theme object | | showThemeSelector | boolean | false | Show theme selector | | borderRadius | 'none' \| 'sm' \| 'md' \| 'lg' \| 'full' | 'md' | Border radius | | previewSize | 'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| '2xl' | 'lg' | Upload icon size | | className | string | '' | Custom class name | | containerClassName | string | 'max-w-5xl mx-auto mt-10' | Container styling | | UI Toggles | | showImageCount | boolean | true | Show image count badge | | showFileName | boolean | true | Show file name under preview | | showFileSize | boolean | true | Show file size under preview | | Custom Button | | customUploadButton | React.ReactNode | undefined | Custom upload button component | | hideDefaultUploadArea | boolean | false | Hide default upload area | | onUploadClick | () => void | undefined | Callback when upload is clicked |

Text Labels Interface

The textLabels prop allows you to customize all text labels in the component for internationalization or custom messaging:

interface ImageUploaderTextLabels {

  // Upload area text
  uploadAreaText?: string;
  uploadAreaDragText?: string;

  // Button text
  uploadButton?: string;
  uploadingButton?: string;

  // Image count
  imageCountLabel?: string;

  // Accessibility
  removeImageLabel?: string;
  uploadImagesLabel?: string;
  dismissErrorLabel?: string;

  // Error messages
  uploadErrorTitle?: string;
  uploadErrorMissingImgBBKey?: string;
  uploadErrorMissingImgBBKeyEmpty?: string;
  uploadErrorMissingCloudinaryName?: string;
  uploadErrorMissingCloudinaryNameEmpty?: string;
}

Examples

Basic Usage

import { ImageUploader } from "ultra-image-uploader";
import { useState } from "react";

function App() {
  const [images, setImages] = useState<File[]>([]);

  return <ImageUploader images={images} setImages={setImages} multiple />;
}

Custom Text Labels (Internationalization)

Customize all text labels for internationalization or custom messaging:

import { ImageUploader } from "ultra-image-uploader";

function SpanishExample() {
  const [images, setImages] = useState<File[]>([]);

  const textLabels = {
    title: "Subir Imágenes",
    uploadAreaText: "Haz clic o arrastra para subir",
    uploadAreaDragText: "Soltar aquí",
    uploadButton: "Subir",
    uploadingButton: "Subiendo...",
    removeImageLabel: "Eliminar imagen",
    dismissErrorLabel: "Descartar",
    uploadErrorTitle: "Error de Subida",
    uploadErrorMissingImgBBKey:
      "Falta la clave API de ImgBB. Proporciona una clave válida.",
    uploadErrorMissingImgBBKeyEmpty: "La clave API de ImgBB no puede estar vacía.",
    uploadErrorMissingCloudinaryName:
      "Falta el nombre de nube de Cloudinary. Proporciona un nombre válido.",
    uploadErrorMissingCloudinaryNameEmpty:
      "El nombre de nube de Cloudinary no puede estar vacío.",
  };

  return (
    <ImageUploader
      images={images}
      setImages={setImages}
      textLabels={textLabels}
      multiple
    />
  );
}

Partial Custom Labels

You only need to override the labels you want to change:

function PartialCustomLabelExample() {
  const [images, setImages] = useState<File[]>([]);

  const textLabels = {
    uploadButton: "Upload Photos",
    uploadingButton: "Uploading your photos...",
    uploadAreaText: "Click here or drop images",
  };

  return (
    <ImageUploader
      images={images}
      setImages={setImages}
      textLabels={textLabels}
    />
  );
}

Different Themes

import { ImageUploader } from "ultra-image-uploader";
import { useState } from "react";

function BasicExample() {
  const [images, setImages] = useState<File[]>([]);

  return <ImageUploader images={images} setImages={setImages} multiple />;
}

Different Themes

// Nature theme (green) - Default
<ImageUploader theme="nature" images={images} setImages={setImages} containerClassName="w-full max-w-2xl mx-auto"/>

// Modern theme (neutral/monochrome)
<ImageUploader theme="modern" images={images} setImages={setImages} containerClassName="w-full max-w-2xl mx-auto"/>

// Fresh theme (blue)
<ImageUploader theme="fresh" images={images} setImages={setImages} containerClassName="w-full max-w-2xl mx-auto"/>

// Dark theme (dark gradient with blue accent)
<ImageUploader theme="dark" images={images} setImages={setImages} containerClassName="w-full max-w-2xl mx-auto"/>

// Ocean theme (blue gradient)
<ImageUploader theme="ocean" images={images} setImages={setImages} containerClassName="w-full max-w-2xl mx-auto"/>

Upload with ImgBB

import { ImageUploader } from "ultra-image-uploader";

function ImgBBUpload() {
  const [images, setImages] = useState<File[]>([]);

  return (
    <ImageUploader
      images={images}
      setImages={setImages}
      multiple
      theme="nature"
      uploadConfig={{
        provider: "imgbb",
        config: { apiKey: process.env.IMGBB_API_KEY! },
      }}
      onUploadComplete={(urls) => {
        console.log("Uploaded URLs:", urls);
      }}
      onUploadError={(error) => {
        console.error("Upload failed:", error.message);
      }}
    />
  );
}

Upload with Cloudinary

function CloudinaryUpload() {
  const [images, setImages] = useState<File[]>([]);

  return (
    <ImageUploader
      images={images}
      setImages={setImages}
      multiple
      theme="fresh"
      uploadConfig={{
        provider: "cloudinary",
        config: {
          cloudName: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME!,
          uploadPreset: process.env.NEXT_PUBLIC_CLOUDINARY_UPLOAD_PRESET!,
        },
      }}
      onUploadComplete={(urls) => {
        console.log("Uploaded URLs:", urls);
      }}
    />
  );
}

Auto Upload

function AutoUploadExample() {
  const [images, setImages] = useState<File[]>([]);

  return (
    <ImageUploader
      images={images}
      setImages={setImages}
      autoUpload
      uploadConfig={{
        provider: "imgbb",
        config: { apiKey: process.env.IMGBB_API_KEY! },
      }}
      onUploadComplete={(urls) => {
        // Automatically upload when images are selected
        saveUrlsToDatabase(urls);
      }}
    />
  );
}

Update Mode (Existing Images)

function UpdateExample() {
  const [newImages, setNewImages] = useState<File[]>([]);

  const existingImages = [
    "https://example.com/image1.jpg",
    "https://example.com/image2.jpg",
  ];

  return (
    <ImageUploader
      images={newImages}
      setImages={setNewImages}
      mode="update"
      defaultImages={existingImages}
      theme="modern"
      multiple
    />
  );
}

Custom Theme

import { ImageUploader, type CustomTheme } from "ultra-image-uploader";

function CustomThemeExample() {
  const [images, setImages] = useState<File[]>([]);

  const customTheme: CustomTheme = {
    name: "MyBrand",
    colors: {
      primary: "#FF6B35",
      primaryHover: "#E55A2B",
      background: "#FFF5F0",
      border: "#FFE5D9",
      text: "#2D3142",
      textSecondary: "#4F5D75",
      cardBg: "#FFFFFF",
      cardBorder: "#FFE5D9",
      shadow: "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)",
    },
  };

  return (
    <ImageUploader
      images={images}
      setImages={setImages}
      customTheme={customTheme}
      showThemeSelector={false}
    />
  );
}

Customization Options

function CustomizedExample() {
  const [images, setImages] = useState<File[]>([]);

  return (
    <ImageUploader
      images={images}
      setImages={setImages}
      // Border radius
      borderRadius="lg"
      // Preview size (affects upload icon)
      previewSize="lg"
      // Container width
      containerClassName="max-w-3xl mx-auto mt-8"
      // Show/hide elements
      showImageCount={true}
      showFileName={true}
      showFileSize={true}
      // Constraints
      multiple={true}
      maxImages={10}
      maxSize={10 * 1024 * 1024} // 10MB
      // Theme
      theme="fresh"
    />
  );
}

Single Image Upload

function SingleImageExample() {
  const [avatar, setAvatar] = useState<File[]>([]);

  return (
    <ImageUploader
      images={avatar}
      setImages={setAvatar}
      multiple={false}
      maxImages={1}
      theme="modern"
      showImageCount={false}
      showFileName={false}
      showFileSize={false}
    />
  );
}

Product Gallery

function ProductGallery() {
  const [productImages, setProductImages] = useState<File[]>([]);

  const handleUploadComplete = async (urls: string[]) => {
    // Save to database
    await fetch("/api/products/images", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ images: urls }),
    });
  };

  return (
    <ImageUploader
      images={productImages}
      setImages={setProductImages}
      multiple={true}
      maxImages={8}
      maxSize={5 * 1024 * 1024} // 5MB
      theme="nature"
      borderRadius="lg"
      uploadConfig={{
        provider: "cloudinary",
        config: {
          cloudName: process.env.CLOUDINARY_CLOUD_NAME!,
          uploadPreset: "products",
        },
      }}
      onUploadComplete={handleUploadComplete}
    />
  );
}

Custom Upload Button

Use your own button for triggering image upload:

function CustomButtonExample() {
  const [images, setImages] = useState<File[]>([]);

  const customButton = (
    <button className="px-6 py-3 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors">
      📷 Choose Images
    </button>
  );

  return (
    <ImageUploader
      images={images}
      setImages={setImages}
      customUploadButton={customButton}
      hideDefaultUploadArea={true}
      onUploadClick={() => console.log("Upload clicked!")}
    />
  );
}

Custom Button with Default Upload Area

Show both your custom button AND the default drag-and-drop area:

function HybridExample() {
  const [images, setImages] = useState<File[]>([]);

  const quickUploadBtn = (
    <button className="px-4 py-2 bg-gradient-to-r from-purple-500 to-pink-500 text-white rounded-lg font-medium">
      Quick Upload
    </button>
  );

  return (
    <ImageUploader
      images={images}
      setImages={setImages}
      customUploadButton={quickUploadBtn}
      // Don't hide the default area - users get both options!
    />
  );
}

Built-in Themes

Nature (Green)

  • Soft greens with organic feel
  • Primary: #16a34a
  • Background: #f0fdf4
  • Perfect for: Nature, health, eco-friendly apps

Modern (Neutral)

  • Clean monochrome design
  • Primary: #09090b
  • Background: #fafafa
  • Perfect for: Professional apps, portfolios, dashboards

Fresh (Blue)

  • Light blue airy design
  • Primary: #0284c7
  • Background: #f0f9ff
  • Perfect for: Social apps, SaaS, modern web apps

Dark (Dark Gradient)

  • Dark gradient with blue accent
  • Primary: #3b82f6
  • Background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%)
  • Perfect for: Dark mode apps, modern interfaces

Ocean (Blue Gradient)

  • Beautiful blue to purple gradient
  • Primary: #06b6d4
  • Background: linear-gradient(135deg, #0ea5e9 0%, #6366f1 100%)
  • Perfect for: Creative apps, vibrant interfaces

Customization

Border Radius Options

borderRadius = "none"; // 0
borderRadius = "sm"; // 0.25rem
borderRadius = "md"; // 0.375rem (default)
borderRadius = "lg"; // 0.5rem
borderRadius = "full"; // 9999px (circular)

Preview Size Options

previewSize = "xs"; // Extra small (40px)
previewSize = "sm"; // Small (48px)
previewSize = "md"; // Medium (56px)
previewSize = "lg"; // Large (64px) - default
previewSize = "xl"; // Extra large (80px)
previewSize = "2xl"; // Double extra large (96px)

Show/Hide Elements

<ImageUploader
  showImageCount={false} // Hide image count badge
  showFileName={false} // Hide file names
  showFileSize={false} // Hide file sizes
/>

Upload Providers

ImgBB

  1. Get API key from imgbb.com/settings/api
  2. Configure:
uploadConfig={{
  provider: 'imgbb',
  config: { apiKey: 'your-api-key' }
}}

Cloudinary

  1. Sign up at cloudinary.com
  2. Get cloud name and create upload preset
  3. Configure:
uploadConfig={{
  provider: 'cloudinary',
  config: {
    cloudName: 'your-cloud-name',
    uploadPreset: 'your-upload-preset'
  }
}}

TypeScript

Full TypeScript support:

import type {
  ImageUploaderProps,
  ThemeName,
  CustomTheme,
} from "ultra-image-uploader";

Animations

The component includes smooth, performance-friendly animations:

  • Drag-over state with border color change
  • Fade-in for image previews
  • Hover effects with shadow and scale
  • Progress overlay with backdrop blur
  • Done indicator with checkmark
  • All transitions use CSS transforms for 60fps performance

Accessibility

  • Keyboard accessible (Tab to focus, Enter/Space to upload)
  • Focus visible on drag area
  • ARIA-compatible markup
  • Screen reader friendly
  • Semantic HTML structure

Responsive Design

The grid layout adapts to screen sizes:

  • Mobile (2 columns): 320px+
  • Tablet (3 columns): 640px+
  • Desktop (4 columns): 768px+
  • Wide (5 columns): 1024px+
  • Default container: max-w-5xl mx-auto mt-10

Browser Support

  • Chrome/Edge (latest)
  • Firefox (latest)
  • Safari (latest)
  • Mobile browsers (iOS Safari, Chrome Mobile)

Error Handling

The component includes built-in API key validation and displays errors inline:

API Key Validation

If your API key is missing or invalid, the component will automatically display an error message:

ImgBB Errors:

  • "ImgBB API key is missing. Please provide a valid API key in the uploadConfig."
  • "ImgBB API key cannot be empty."

Cloudinary Errors:

  • "Cloudinary cloud name is missing. Please provide a valid cloud name in the uploadConfig."
  • "Cloudinary cloud name cannot be empty."

Error Display

Errors appear in a dismissible alert box below the header with:

  • Red error icon
  • Clear error message
  • Dismiss button to clear the error

Handling Upload Errors

You can also handle errors programmatically using the onUploadError callback:

<ImageUploader
  images={images}
  setImages={setImages}
  uploadConfig={{
    provider: "imgbb",
    config: { apiKey: process.env.IMGBB_API_KEY! },
  }}
  onUploadError={(error) => {
    console.error("Upload failed:", error);
    // Show custom notification
    toast.error(error.message);
  }}
/>

Troubleshooting

Auto imports not working

  • Restart TypeScript server in your editor (Cmd+Shift+P > "Restart TypeScript Server")
  • Ensure node_modules exists (npm install)

Images not uploading

  • Verify API credentials in environment variables
  • Check browser console for errors
  • Ensure CORS is configured for your upload provider
  • Look for inline error messages in the component

Theme not applying

  • Check that theme name matches: 'nature' | 'modern' | 'fresh' | 'dark' | 'ocean'
  • For custom themes, verify the structure matches CustomTheme type

License

MIT © Digontha Das

Links


Made with ❤️ by Digontha Das