# File Upload Multi-File Dropzone

> A file upload multi-file dropzone block.

A shadcn/ui block from the [@blocks-so](https://blocks.so) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/blocks-so/file-upload-03).

- Page, with a live preview: https://shadcn.exchange/r/blocks-so/file-upload-03
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/blocks-so/file-upload-03-light-b8f4de40cadc1305.png
- Registry item: https://blocks.so/r/file-upload-03.json
- Categories: file-upload
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://blocks.so/r/file-upload-03.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `lucide-react`, `react-dropzone`
- Registry items: `button`, `card`, `input`, `label`, `select`, `separator`

## Files

### `content/components/file-upload/file-upload-03.tsx`

```tsx
'use client';

import { File, Trash } from 'lucide-react';
import React from 'react';
import { useDropzone } from 'react-dropzone';

import { Button } from '@/components/ui/button';
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '@/components/ui/select';
import { Separator } from '@/components/ui/separator';
import { cn } from '@/lib/utils';

export default function FileUpload03() {
  const [files, setFiles] = React.useState<File[]>([]);
  const { getRootProps, getInputProps, isDragActive } = useDropzone({
    onDrop: (acceptedFiles) => setFiles(acceptedFiles),
  });

  const filesList = files.map((file) => (
    <li className="relative" key={file.name}>
      <Card className="relative p-4 shadow-none">
        <div className="-translate-y-1/2 absolute top-1/2 right-4">
          <Button
            aria-label="Remove file"
            onClick={() =>
              setFiles((prevFiles) =>
                prevFiles.filter((prevFile) => prevFile.name !== file.name)
              )
            }
            size="icon"
            type="button"
            variant="ghost"
          >
            <Trash aria-hidden={true} className="h-5 w-5" />
          </Button>
        </div>
        <CardContent className="flex items-center space-x-3 p-0">
          <span className="flex h-10 w-10 shrink-0 items-center justify-center rounded-md bg-muted">
            <File aria-hidden={true} className="h-5 w-5 text-foreground" />
          </span>
          <div>
            <p className="text-pretty font-medium text-foreground">
              {file.name}
            </p>
            <p className="mt-0.5 text-pretty text-muted-foreground text-sm">
              {file.size} bytes
            </p>
          </div>
        </CardContent>
      </Card>
    </li>
  ));

  return (
    <div className="flex items-center justify-center p-10">
      <Card className="shadow-none sm:mx-auto sm:max-w-xl">
        <CardHeader>
          <CardTitle>Set up your first cloud storage</CardTitle>
          <CardDescription>
            Lorem ipsum dolor sit amet, consetetur sadipscing elitr.
          </CardDescription>
        </CardHeader>
        <CardContent>
          <form action="#" method="post">
            <div className="grid grid-cols-1 gap-4 sm:grid-cols-6">
              <div className="col-span-full sm:col-span-3">
                <Label className="font-medium" htmlFor="bucket-name">
                  Bucket name
                </Label>
                <Input
                  className="mt-2"
                  id="bucket-name"
                  name="bucket-name"
                  placeholder="Bucket name"
                  type="text"
                />
              </div>
              <div className="col-span-full sm:col-span-3">
                <Label className="font-medium" htmlFor="visibility">
                  Visibility
                </Label>
                <Select
                  defaultValue="private"
                  disabled
                  items={{ private: 'Private', public: 'Public' }}
                >
                  <SelectTrigger
                    className="mt-2 w-full"
                    id="visibility"
                    name="visibility"
                  >
                    <SelectValue placeholder="Select visibility" />
                  </SelectTrigger>
                  <SelectContent>
                    <SelectItem value="private">Private</SelectItem>
                    <SelectItem value="public">Public</SelectItem>
                  </SelectContent>
                </Select>
                <p className="mt-2 text-pretty text-muted-foreground text-sm">
                  Only admins can change visibility.
                </p>
              </div>
              <div className="col-span-full">
                <Label className="font-medium" htmlFor="file-upload-2">
                  File(s) upload
                </Label>
                <div
                  {...getRootProps()}
                  className={cn(
                    isDragActive
                      ? 'border-primary bg-primary/10 ring-2 ring-primary/20'
                      : 'border-border',
                    'mt-2 flex justify-center rounded-md border border-dashed px-6 py-20 transition-colors duration-200'
                  )}
                >
                  <div>
                    <File
                      aria-hidden={true}
                      className="mx-auto h-12 w-12 text-muted-foreground/80"
                    />
                    <div className="mt-4 flex text-muted-foreground">
                      <p>Drag and drop or</p>
                      <label
                        className="relative cursor-pointer rounded-sm pl-1 font-medium text-primary hover:text-primary/80 hover:underline hover:underline-offset-4"
                        htmlFor="file"
                      >
                        <span>choose file(s)</span>
                        <input
                          {...getInputProps()}
                          className="sr-only"
                          id="file-upload-2"
                          name="file-upload-2"
                          type="file"
                        />
                      </label>
                      <p className="text-pretty pl-1">to upload</p>
                    </div>
                  </div>
                </div>
                <p className="mt-2 text-pretty text-muted-foreground text-sm leading-5 sm:flex sm:items-center sm:justify-between">
                  <span>All file types are allowed to upload.</span>
                  <span className="pl-1 sm:pl-0">Max. size per file: 50MB</span>
                </p>
                {filesList.length > 0 && (
                  <>
                    <h4 className="mt-6 text-balance font-medium text-foreground">
                      File(s) to upload
                    </h4>
                    <ul className="mt-4 space-y-4" role="list">
                      {filesList}
                    </ul>
                  </>
                )}
              </div>
            </div>
            <Separator className="my-6" />
            <div className="flex items-center justify-end space-x-3">
              <Button type="button" variant="outline">
                Cancel
              </Button>
              <Button type="submit">Upload</Button>
            </div>
          </form>
        </CardContent>
      </Card>
    </div>
  );
}
```
