Dropzone

Capture files from user with drag and drop

License

Installation

yarn add @mantine/dropzone

After installation import package styles at the root of your application:

import '@mantine/dropzone/styles.css';

Usage

Dropzone lets you capture one or more files from user. Component is based on react-dropzone and support all of its core features:

  • Accepts/rejects files based on provided mime types
  • Limits individual file size
  • Renders given children and provides context based component to display elements based on current status
import { Group, Text, rem } from '@mantine/core';
import { IconUpload, IconPhoto, IconX } from '@tabler/icons-react';
import { Dropzone, DropzoneProps, IMAGE_MIME_TYPE } from '@mantine/dropzone';

export function BaseDemo(props: Partial<DropzoneProps>) {
  return (
    <Dropzone
      onDrop={(files) => console.log('accepted files', files)}
      onReject={(files) => console.log('rejected files', files)}
      maxSize={5 * 1024 ** 2}
      accept={IMAGE_MIME_TYPE}
      {...props}
    >
      <Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
        <Dropzone.Accept>
          <IconUpload
            style={{ width: rem(52), height: rem(52), color: 'var(--mantine-color-blue-6)' }}
            stroke={1.5}
          />
        </Dropzone.Accept>
        <Dropzone.Reject>
          <IconX
            style={{ width: rem(52), height: rem(52), color: 'var(--mantine-color-red-6)' }}
            stroke={1.5}
          />
        </Dropzone.Reject>
        <Dropzone.Idle>
          <IconPhoto
            style={{ width: rem(52), height: rem(52), color: 'var(--mantine-color-dimmed)' }}
            stroke={1.5}
          />
        </Dropzone.Idle>

        <div>
          <Text size="xl" inline>
            Drag images here or click to select files
          </Text>
          <Text size="sm" c="dimmed" inline mt={7}>
            Attach as many files as you like, each file should not exceed 5mb
          </Text>
        </div>
      </Group>
    </Dropzone>
  );
}

Dropzone.Accept, Dropzone.Reject and Dropzone.Idle

Dropzone.Accept, Dropzone.Reject and Dropzone.Idle components are visible only when the user performs certain action:

  • Dropzone.Accept is visible only when the user drags file that can be accepted over the dropzone
  • Dropzone.Reject is visible only when the user drags file that cannot be accepted over the dropzone
  • Dropzone.Idle is visible when the user does not drag anything over dropzone

Loading state

Set loading prop to indicate loading state with LoadingOverlay component. When loading props is true user cannot drop or select new files (Dropzone becomes disabled):

import { Dropzone } from '@mantine/dropzone';

function Demo() {
  return (
    <Dropzone loading onDrop={() => {}}>
      {/* children */}
    </Dropzone>
  );
}

Disabled state

If you want to implement your own loading state you can disable Dropzone without LoadingOverlay. Same as with loading, when Dropzone is disabled user cannot drop or select new files:

import { Dropzone } from '@mantine/dropzone';
import classes from './Demo.module.css';

function Demo() {
  return (
    <Dropzone disabled className={classes.disabled} onDrop={() => {}}>
      {/* children... */}
    </Dropzone>
  );
}

Open file browser manually

To open files browser from outside of component use openRef prop to get function that will trigger file browser:

import { useRef } from 'react';
import { Button, Group } from '@mantine/core';
import { Dropzone } from '@mantine/dropzone';

function Demo() {
  const openRef = useRef<() => void>(null);

  return (
    <>
      <Dropzone openRef={openRef} onDrop={() => {}}>
        {/* children */}
      </Dropzone>

      <Group justify="center" mt="md">
        <Button onClick={() => openRef.current?.()}>Select files</Button>
      </Group>
    </>
  );
}

Enable child pointer event

By default, Dropzone disables pointer events on its children for dragging events to work. When activateOnClick={false}, clicking on any children inside Dropzone will not do anything. However, you can set style pointerEvents: 'all' to make children clickable. Note that you need to set these styles only on interactive elements, such as buttons or links.

import { useRef } from 'react';
import { Button, Group } from '@mantine/core';
import { Dropzone } from '@mantine/dropzone';

function Demo() {
  const openRef = useRef<() => void>(null);

  return (
    <Dropzone openRef={openRef} onDrop={() => {}} activateOnClick={false}>
      <Group justify="center">
        <Button onClick={() => openRef.current?.()} style={{ pointerEvents: 'all' }}>
          Select files
        </Button>
      </Group>
    </Dropzone>
  );
}

Mime types

To specify file types provide an object with the keys set to the mime type and the values as an array of file extensions. Find more examples of accepting specific file types in the react-dropzone documentation.

import { Dropzone } from '@mantine/dropzone';

function Demo() {
  return (
    <Dropzone
      accept={{
        'image/*': [], // All images
        'text/html': ['.html', '.htm'],
      }}
      onDrop={() => {}}
    >
      {/* children */}
    </Dropzone>
  );
}

You can also specify file types by providing an array of mime types to accept prop:

import { Dropzone } from '@mantine/dropzone';

function Demo() {
  return (
    <Dropzone
      accept={[
        'image/png',
        'image/jpeg',
        'image/sgv+xml',
        'image/gif',
      ]}
      onDrop={() => {}}
    >
      {/* children */}
    </Dropzone>
  );
}

To save some research time you can use MIME_TYPES variable exported from @mantine/dropzone:

import { Dropzone, MIME_TYPES } from '@mantine/dropzone';

function Demo() {
  return (
    <Dropzone
      accept={[
        MIME_TYPES.png,
        MIME_TYPES.jpeg,
        MIME_TYPES.svg,
        MIME_TYPES.gif,
      ]}
      onDrop={() => {}}
    >
      {/* children */}
    </Dropzone>
  );
}

MIME_TYPES includes following data:

KeyMime type
pngimage/png
gifimage/gif
jpegimage/jpeg
svgimage/svg+xml
webpimage/webp
avifimage/avif
heicimage/heic
mp4video/mp4
zipapplication/zip
csvtext/csv
pdfapplication/pdf
docapplication/msword
docxapplication/vnd.openxmlformats-officedocument.wordprocessingml.document
xlsapplication/vnd.ms-excel
xlsxapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet
pptapplication/vnd.ms-powerpoint
pptxapplication/vnd.openxmlformats-officedocument.presentationml.presentation
exeapplication/vnd.microsoft.portable-executable

Additionally you can use grouped mime types:

VariableMime types
IMAGE_MIME_TYPEimage/png, image/gif, image/jpeg, image/svg+xml, image/webp, image/avif, image/heic
PDF_MIME_TYPEapplication/pdf
MS_WORD_MIME_TYPEapplication/msword, application/vnd.openxmlformats-officedocument.wordprocessingml.document
MS_EXCEL_MIME_TYPEapplication/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
MS_POWERPOINT_MIME_TYPEapplication/vnd.ms-powerpoint, application/vnd.openxmlformats-officedocument.presentationml.presentation
import { Dropzone, IMAGE_MIME_TYPE } from '@mantine/dropzone';

function Demo() {
  return (
    <Dropzone accept={IMAGE_MIME_TYPE} onDrop={() => {}}>
      {/* children */}
    </Dropzone>
  );
}

Styles API

Dropzone root element has the following data attributes to change styles based on current status:

  • data-loading – when loading prop is true
  • data-accept – when user drags files that can be accepted over the dropzone
  • data-reject – when user drags files that cannot be accepted over the dropzone
  • data-idle – default state – user does not drag any files over dropzone
import { Text } from '@mantine/core';
import { Dropzone, IMAGE_MIME_TYPE } from '@mantine/dropzone';
import classes from './Demo.module.css';

function Demo() {
  return (
    <Dropzone onDrop={() => {}} accept={IMAGE_MIME_TYPE} className={classes.root}>
      <Text ta="center">Drop images here</Text>
    </Dropzone>
  );
}

Images previews

import { useState } from 'react';
import { Text, Image, SimpleGrid } from '@mantine/core';
import { Dropzone, IMAGE_MIME_TYPE, FileWithPath } from '@mantine/dropzone';

function Demo() {
  const [files, setFiles] = useState<FileWithPath[]>([]);

  const previews = files.map((file, index) => {
    const imageUrl = URL.createObjectURL(file);
    return <Image key={index} src={imageUrl} onLoad={() => URL.revokeObjectURL(imageUrl)} />;
  });

  return (
    <div>
      <Dropzone accept={IMAGE_MIME_TYPE} onDrop={setFiles}>
        <Text ta="center">Drop images here</Text>
      </Dropzone>

      <SimpleGrid cols={{ base: 1, sm: 4 }} mt={previews.length > 0 ? 'xl' : 0}>
        {previews}
      </SimpleGrid>
    </div>
  );
}

Get ref

import { useEffect, useRef } from 'react';
import { Dropzone } from '@mantine/dropzone';

function Demo() {
  const dropzoneRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    dropzoneRef.current?.focus();
  }, []);

  return (
    <Dropzone ref={dropzoneRef} onDrop={() => {}}>
      {/* children */}
    </Dropzone>
  );
}

Dropzone.FullScreen component

Dropzone.FullScreen lets you capture files dropped to browser window instead of specific area. It supports the same props as Dropzone component.

To preview component click button and drop images to browser window:

import { useState } from 'react';
import { Group, Text, Button, rem } from '@mantine/core';
import { IconUpload, IconPhoto, IconX } from '@tabler/icons-react';
import { Dropzone, IMAGE_MIME_TYPE } from '@mantine/dropzone';

function Demo() {
  const [active, setActive] = useState(false);

  return (
    <>
      <Group justify="center">
        <Button color={active ? 'red' : 'blue'} onClick={() => setActive((d) => !d)}>
          {active ? 'Deactivate' : 'Activate'} full screen dropzone
        </Button>
      </Group>

      <Dropzone.FullScreen
        active={active}
        accept={IMAGE_MIME_TYPE}
        onDrop={(files) => {
          console.log(files);
          setActive(false);
        }}
      >
        <Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
          <Dropzone.Accept>
            <IconUpload
              style={{ width: rem(52), height: rem(52), color: 'var(--mantine-color-blue-6)' }}
              stroke={1.5}
            />
          </Dropzone.Accept>
          <Dropzone.Reject>
            <IconX
              style={{ width: rem(52), height: rem(52), color: 'var(--mantine-color-red-6)' }}
              stroke={1.5}
            />
          </Dropzone.Reject>
          <Dropzone.Idle>
            <IconPhoto
              style={{ width: rem(52), height: rem(52), color: 'var(--mantine-color-dimmed)' }}
              stroke={1.5}
            />
          </Dropzone.Idle>

          <div>
            <Text size="xl" inline>
              Drag images here or click to select files
            </Text>
            <Text size="sm" c="dimmed" inline mt={7}>
              Attach as many files as you like, each file should not exceed 5mb
            </Text>
          </div>
        </Group>
      </Dropzone.FullScreen>
    </>
  );
}