A reusable, responsive multi-image upload component built with Next.js, TypeScript, Tailwind CSS, shadcn/ui
, and react-hook-form
. This project provides a simple, type-safe solution for uploading multiple images with progress tracking, deletion feedback, and form validation integration.
- Multi-Image Upload: Upload multiple files with a configurable maximum limit (default: 5).
- Responsive Design: Flex-based layout that adapts to different screen sizes using Tailwind CSS.
- Progress Tracking: Displays upload progress as a percentage overlay on each image.
- Deletion Feedback: Glow-and-dim animation during image deletion to indicate the action is in progress.
- Form Integration: Fully compatible with
react-hook-form
for controlled form validation and error handling. - Type-Safe: Written in TypeScript with proper type definitions for props and internal state.
- Customizable: Easily styled via Tailwind CSS classes and extensible through props.
- API Support: Integrates with signed URL generation and file upload/deletion APIs (server-side logic included).
- Node.js (v18+ recommended)
- Bun (or npm/yarn/pnpm) as the package manager
- Next.js project with TypeScript configured
- Tailwind CSS installed
shadcn/ui
components (button
,form
,label
) installedreact-hook-form
and@hookform/resolvers/zod
for form validationzod
for schema validationlucide-react
for icons
-
Clone the Repository (optional, if not copying files directly):
git clone https://github.com/jacksonkasi0/nextjs-multi-image-upload.git cd nextjs-multi-image-upload
-
Install Dependencies:
bun install
-
Set Up Environment Variables:
-
Copy
example.env
to.env
and configure your API keys or endpoints (e.g., for signed URL generation). -
Example
.env
:NODE_ENV="production" # S3 AWS_ACCESS_KEY_ID="xxxxx" # your access key AWS_SECRET_ACCESS_KEY="xxxxxxx" # your secret key AWS_REGION="xxxxxx" # example: ap-south-1, us-east-1 AWS_BUCKET_NAME="xxxxx" # your bucket name
π Check the
Secure_S3_Bucket_Setup.md
guide for setting up a secure S3 bucket. π‘οΈ -
-
Run the Development Server:
bun dev
Open http://localhost:3000 to see the app.
To use the MultiImageUpload
component in your own Next.js project, follow these steps:
Copy the following files from this repository into your project:
src/components/multi-image-upload.tsx
: The core component with upload and deletion logic.src/api/upload-api.ts
: Client-side API functions for signed URL generation, uploading, and deletion.src/app/api/upload/signed-url/route.ts
: Server-side route to generate signed URLs.src/app/api/upload/delete/route.ts
: Server-side route to delete files.src/components/background.tsx
: Optional background component for the radial gradient effect.
Ensure your project has the required dependencies. Add them to your package.json
:
bun add react-hook-form @hookform/resolvers/zod zod lucide-react
bun add -D tailwindcss postcss autoprefixer @types/react @types/node typescript
If youβre using shadcn/ui
, initialize it and add the necessary components:
npx shadcn-ui@latest init
npx shadcn-ui@latest add button form label
To ensure the MultiImageUpload
component works correctly with its glow-and-dim animation during deletion, configure your tailwind.config.ts
with the shadcn/ui
color variables (e.g., --muted
, --primary
) and the custom glow-effect
animation. Below is an example configuration:
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ["class"],
content: [
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
colors: {
// shadcn/ui color definitions...
},
borderRadius: {
// shadcn/ui radius definitions...
},
animation: {
"glow-effect": "glow-effect 1.5s infinite ease-in-out",
},
keyframes: {
"glow-effect": {
"0%, 100%": {
boxShadow:
"0 0 10px var(--muted-foreground), 0 0 20px var(--muted)",
opacity: "1",
},
"50%": {
boxShadow:
"0 0 20px var(--primary), 0 0 40px var(--primary-foreground)",
opacity: "0.5",
},
},
},
},
},
plugins: [require("tailwindcss-animate")],
};
Important Notes:
-
Animation: The
glow-effect
animation is critical for deletion feedback inMultiImageUpload
. You must include theanimation
andkeyframes
definitions as shown above. It usesbox-shadow
andopacity
, relying onshadcn/ui
color variables (--muted
,--muted-foreground
,--primary
,--primary-foreground
). -
Plugin: Add the
tailwindcss-animate
plugin to enable custom animations. Install it with:bun add -D tailwindcss-animate
-
Colors: Ensure your
shadcn/ui
setup defines the required CSS variables inglobals.css
(e.g.,:root { --muted: 210 40% 96.1%; }
). If not, replace the variables with specific HSL or hex values.
Hereβs how to use MultiImageUpload
with react-hook-form
in a page (e.g., src/app/page.tsx
):
"use client";
import React from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { MultiImageUpload } from "@/components/multi-image-upload";
import Background from "@/components/background";
const schema = z.object({
images: z
.array(z.string().url())
.min(1, "At least 1 image is required")
.max(5, "Maximum 5 images allowed"),
});
type FormData = z.infer<typeof schema>;
export default function Page() {
const form = useForm<FormData>({
resolver: zodResolver(schema),
defaultValues: { images: [] },
});
const onSubmit = (data: FormData) => {
console.log("Submitted:", data.images);
};
return (
<Background>
<div className="min-h-screen flex items-center justify-center p-4">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-6 w-full max-w-lg"
>
<FormField
control={form.control}
name="images"
render={({ field }) => (
<FormItem>
<FormLabel>Images</FormLabel>
<FormControl>
<MultiImageUpload
value={field.value}
onChange={field.onChange}
maxImages={5}
className="my-4"
imageRegex={/\.(jpeg|jpg|png|gif|webp|avif)$/i}
accept="image/*"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" className="w-full">
Submit
</Button>
</form>
</Form>
</div>
</Background>
);
}
- Place
signed-url/route.ts
anddelete/route.ts
in yoursrc/app/api/upload/
directory. - Update the API logic in
upload-api.ts
to point to your backend (e.g., AWS S3, Cloudinary) if different from the example.
Prop | Type | Default | Description |
---|---|---|---|
value |
string[] |
[] |
Controlled array of file URLs |
onChange |
(images: string[]) => void |
- | Callback to update the value |
maxImages |
number |
- | Optional maximum number of files allowed |
className |
string |
- | Additional Tailwind CSS classes for styling |
name |
string |
- | Optional field name for React Hook Form integration |
imageRegex |
RegExp |
/\.(jpeg|jpg|png|gif|webp|avif)$/i |
Regex to detect image formats for initial value previews |
accept |
string |
"image/*" |
MIME types or file extensions accepted by the file input (e.g., "image/png, image/jpeg" ) |
Notes:
imageRegex
determines which initial URLs are treated as images for preview rendering. Customize it to support additional formats (e.g.,.svg
).accept
restricts the file picker to specific types during uploads. Use MIME types or extensions (e.g.,"image/*,application/pdf"
).
nextjs-multi-image-upload/
βββ public/ # Static assets
β βββ file.svg
β βββ globe.svg
β βββ next.svg
β βββ vercel.svg
β βββ window.svg
βββ src/
β βββ api/
β β βββ upload-api.ts # Client-side API functions
β βββ app/
β β βββ api/upload/
β β β βββ delete/route.ts # DELETE route for file removal
β β β βββ signed-url/route.ts # GET/POST route for signed URLs
β β βββ favicon.ico
β β βββ layout.tsx
β β βββ page.tsx # Example page using MultiImageUpload
β βββ components/
β β βββ ui/ # shadcn/ui components
β β β βββ button.tsx
β β β βββ form.tsx
β β β βββ label.tsx
β β βββ background.tsx # Radial gradient background
β β βββ multi-image-upload.tsx # Main component
β βββ config/
β β βββ env.ts # Environment variable config
β βββ helpers/
β β βββ upload.ts # Upload-related utilities
β βββ lib/
β β βββ utils.ts # Utility functions (e.g., cn)
β βββ styles/
β βββ globals.css # Global styles
βββ .env # Environment variables
βββ .gitignore
βββ README.md
βββ bun.lock # Bun lockfile
βββ components.json # shadcn/ui config
βββ eslint.config.mjs
βββ example.env
βββ next-env.d.ts
βββ next.config.ts
βββ package.json
βββ postcss.config.mjs
βββ tailwind.config.ts
βββ tsconfig.json
- Fork the repository.
- Create a feature branch (
git checkout -b feature/new-feature
). - Commit changes (
git commit -m "Add new feature"
). - Push to the branch (
git push origin feature/new-feature
). - Open a pull request.
MIT License. See LICENSE for details.
Created by jacksonkasi0 & jacksonkasi1.