Drop zone

The drop zone component lets users upload files by dragging and dropping the files into an area on a page, or activating a button.

Examples

Use to allow merchants to upload files. They can drag and drop files into the dashed area, or upload traditionally by clicking the “Add file” button or anywhere inside the dashed area.

import {DropZone, Stack, Thumbnail, Caption} from '@shopify/polaris';
import {NoteMinor} from '@shopify/polaris-icons';
import {useState, useCallback} from 'react';

function DropZoneExample() {
  const [files, setFiles] = useState([]);

  const handleDropZoneDrop = useCallback(
    (_dropFiles, acceptedFiles, _rejectedFiles) =>
      setFiles((files) => [...files, ...acceptedFiles]),
    [],
  );

  const validImageTypes = ['image/gif', 'image/jpeg', 'image/png'];

  const fileUpload = !files.length && <DropZone.FileUpload />;
  const uploadedFiles = files.length > 0 && (
    <div style={{padding: '0'}}>
      <Stack vertical>
        {files.map((file, index) => (
          <Stack alignment="center" key={index}>
            <Thumbnail
              size="small"
              alt={file.name}
              source={
                validImageTypes.includes(file.type)
                  ? window.URL.createObjectURL(file)
                  : NoteMinor
              }
            />
            <div>
              {file.name} <Caption>{file.size} bytes</Caption>
            </div>
          </Stack>
        ))}
      </Stack>
    </div>
  );

  return (
    <DropZone onDrop={handleDropZoneDrop}>
      {uploadedFiles}
      {fileUpload}
    </DropZone>
  );
}

Use to pair with a label for better accessibility.

import {DropZone} from '@shopify/polaris';
import React from 'react';

function DropZoneExample() {
  return (
    <DropZone label="Theme files">
      <DropZone.FileUpload />
    </DropZone>
  );
}

Use for cases that accept image file formats.

import {
  DropZone,
  Stack,
  Thumbnail,
  Caption,
  Banner,
  List,
} from '@shopify/polaris';
import {useState, useCallback} from 'react';

function DropZoneWithImageFileUpload() {
  const [files, setFiles] = useState([]);
  const [rejectedFiles, setRejectedFiles] = useState([]);
  const hasError = rejectedFiles.length > 0;

  const handleDrop = useCallback(
    (_droppedFiles, acceptedFiles, rejectedFiles) => {
      setFiles((files) => [...files, ...acceptedFiles]);
      setRejectedFiles(rejectedFiles);
    },
    [],
  );

  const fileUpload = !files.length && <DropZone.FileUpload />;
  const uploadedFiles = files.length > 0 && (
    <Stack vertical>
      {files.map((file, index) => (
        <Stack alignment="center" key={index}>
          <Thumbnail
            size="small"
            alt={file.name}
            source={window.URL.createObjectURL(file)}
          />
          <div>
            {file.name} <Caption>{file.size} bytes</Caption>
          </div>
        </Stack>
      ))}
    </Stack>
  );

  const errorMessage = hasError && (
    <Banner
      title="The following images couldn’t be uploaded:"
      status="critical"
    >
      <List type="bullet">
        {rejectedFiles.map((file, index) => (
          <List.Item key={index}>
            {`"${file.name}" is not supported. File type must be .gif, .jpg, .png or .svg.`}
          </List.Item>
        ))}
      </List>
    </Banner>
  );

  return (
    <Stack vertical>
      {errorMessage}
      <DropZone accept="image/*" type="image" onDrop={handleDrop}>
        {uploadedFiles}
        {fileUpload}
      </DropZone>
    </Stack>
  );
}

Use to accept only one file.

import {DropZone, Stack, Thumbnail, Caption} from '@shopify/polaris';
import {NoteMinor} from '@shopify/polaris-icons';
import {useState, useCallback} from 'react';

function DropZoneExample() {
  const [file, setFile] = useState();

  const handleDropZoneDrop = useCallback(
    (_dropFiles, acceptedFiles, _rejectedFiles) =>
      setFile((file) => acceptedFiles[0]),
    [],
  );

  const validImageTypes = ['image/gif', 'image/jpeg', 'image/png'];

  const fileUpload = !file && <DropZone.FileUpload />;
  const uploadedFile = file && (
    <Stack>
      <Thumbnail
        size="small"
        alt={file.name}
        source={
          validImageTypes.includes(file.type)
            ? window.URL.createObjectURL(file)
            : NoteMinor
        }
      />
      <div>
        {file.name} <Caption>{file.size} bytes</Caption>
      </div>
    </Stack>
  );

  return (
    <DropZone allowMultiple={false} onDrop={handleDropZoneDrop}>
      {uploadedFile}
      {fileUpload}
    </DropZone>
  );
}

Use to accept files for upload when dropped anywhere on the page.

import {Stack, Thumbnail, Caption, DropZone, Page} from '@shopify/polaris';
import {NoteMinor} from '@shopify/polaris-icons';
import {useState, useCallback} from 'react';

function DropZoneWithDropOnPageExample() {
  const [files, setFiles] = useState([]);

  const handleDropZoneDrop = useCallback(
    (dropFiles, _acceptedFiles, _rejectedFiles) =>
      setFiles((files) => [...files, ...dropFiles]),
    [],
  );

  const validImageTypes = ['image/gif', 'image/jpeg', 'image/png'];

  const uploadedFiles = files.length > 0 && (
    <Stack vertical>
      {files.map((file, index) => (
        <Stack alignment="center" key={index}>
          <Thumbnail
            size="small"
            alt={file.name}
            source={
              validImageTypes.includes(file.type)
                ? window.URL.createObjectURL(file)
                : NoteMinor
            }
          />
          <div>
            {file.name} <Caption>{file.size} bytes</Caption>
          </div>
        </Stack>
      ))}
    </Stack>
  );

  const uploadMessage = !uploadedFiles && <DropZone.FileUpload />;

  return (
    <Page
      breadcrumbs={[{content: 'Products'}]}
      title="Jar With Lock-Lid"
      primaryAction={{content: 'Save', disabled: true}}
      secondaryActions={[
        {content: 'Duplicate'},
        {content: 'View on your store'},
      ]}
      pagination={{
        hasPrevious: true,
        hasNext: true,
      }}
    >
      <DropZone dropOnPage onDrop={handleDropZoneDrop}>
        {uploadedFiles}
        {uploadMessage}
      </DropZone>
    </Page>
  );
}

Use to accept only SVG files.

import {
  Stack,
  Thumbnail,
  Caption,
  Banner,
  List,
  DropZone,
} from '@shopify/polaris';
import {useState, useCallback} from 'react';

function DropZoneAcceptingSVGFilesExample() {
  const [files, setFiles] = useState([]);
  const [rejectedFiles, setRejectedFiles] = useState([]);
  const hasError = rejectedFiles.length > 0;

  const handleDropZoneDrop = useCallback(
    (_dropFiles, acceptedFiles, rejectedFiles) => {
      setFiles((files) => [...files, ...acceptedFiles]);
      setRejectedFiles(rejectedFiles);
    },
    [],
  );

  const uploadedFiles = files.length > 0 && (
    <Stack vertical>
      {files.map((file, index) => (
        <Stack alignment="center" key={index}>
          <Thumbnail
            size="small"
            alt={file.name}
            source={window.URL.createObjectURL(file)}
          />
          <div>
            {file.name} <Caption>{file.size} bytes</Caption>
          </div>
        </Stack>
      ))}
    </Stack>
  );

  const errorMessage = hasError && (
    <Banner
      title="The following images couldn’t be uploaded:"
      status="critical"
    >
      <List type="bullet">
        {rejectedFiles.map((file, index) => (
          <List.Item key={index}>
            {`"${file.name}" is not supported. File type must be .svg.`}
          </List.Item>
        ))}
      </List>
    </Banner>
  );

  return (
    <Stack vertical>
      {errorMessage}
      <DropZone
        accept="image/svg+xml"
        type="image"
        errorOverlayText="File type must be .svg"
        onDrop={handleDropZoneDrop}
      >
        {uploadedFiles}
      </DropZone>
    </Stack>
  );
}

Use to allow merchants to upload files in a wider area than the visible drop zone.

import {DropZone, Stack, Thumbnail, Caption, Card} from '@shopify/polaris';
import {NoteMinor} from '@shopify/polaris-icons';
import {useState, useCallback} from 'react';

function NestedDropZoneExample() {
  const [files, setFiles] = useState([]);

  const handleDrop = useCallback((dropFiles) => {
    setFiles((files) => [...files, dropFiles]);
  }, []);

  const handleDropZoneClick = useCallback(() => {}, []);

  const validImageTypes = ['image/gif', 'image/jpeg', 'image/png'];

  const fileUpload = !files.length && <DropZone.FileUpload />;
  const uploadedFiles = files.length > 0 && (
    <Stack vertical>
      {files.map((file, index) => (
        <Stack alignment="center" key={index}>
          <Thumbnail
            size="small"
            alt={file.name}
            source={
              validImageTypes.includes(file.type)
                ? window.URL.createObjectURL(file)
                : NoteMinor
            }
          />
          <div>
            {file.name} <Caption>{file.size} bytes</Caption>
          </div>
        </Stack>
      ))}
    </Stack>
  );

  return (
    <DropZone outline={false} onDrop={handleDrop}>
      <Card sectioned>
        <DropZone onClick={handleDropZoneClick}>
          {uploadedFiles}
          {fileUpload}
        </DropZone>
      </Card>
    </DropZone>
  );
}

Use for cases with limited space. To improve usability, nest medium-sized drop zone in a larger drop zone with no outline. See the nested dropzone example.

import {DropZone} from '@shopify/polaris';
import React from 'react';

function DropZoneExample() {
  return (
    <div style={{width: 114, height: 114}}>
      <DropZone>
        <DropZone.FileUpload />
      </DropZone>
    </div>
  );
}

Use for cases with tight space constraints, such as variant thumbnails on the Product details page. To improve usability, nest small-sized drop zone in a larger drop zone with no outline. See the nested dropzone example.

import {DropZone} from '@shopify/polaris';
import React from 'react';

function DropZoneExample() {
  return (
    <div style={{width: 50, height: 50}}>
      <DropZone>
        <DropZone.FileUpload />
      </DropZone>
    </div>
  );
}

Use for cases where you want the child contents of the dropzone to determine its height.

import {DropZone, Stack, Thumbnail, Caption} from '@shopify/polaris';
import {NoteMinor} from '@shopify/polaris-icons';
import {useState, useCallback} from 'react';

function DropZoneExample() {
  const [files, setFiles] = useState([]);

  const handleDropZoneDrop = useCallback(
    (_dropFiles, acceptedFiles, _rejectedFiles) =>
      setFiles((files) => [...files, ...acceptedFiles]),
    [],
  );

  const validImageTypes = ['image/gif', 'image/jpeg', 'image/png'];

  const fileUpload = !files.length && (
    <DropZone.FileUpload actionHint="Accepts .gif, .jpg, and .png" />
  );

  const uploadedFiles = files.length > 0 && (
    <Stack vertical>
      {files.map((file, index) => (
        <Stack alignment="center" key={index}>
          <Thumbnail
            size="small"
            alt={file.name}
            source={
              validImageTypes.includes(file.type)
                ? window.URL.createObjectURL(file)
                : NoteMinor
            }
          />
          <div>
            {file.name} <Caption>{file.size} bytes</Caption>
          </div>
        </Stack>
      ))}
    </Stack>
  );

  return (
    <DropZone onDrop={handleDropZoneDrop} variableHeight>
      {uploadedFiles}
      {fileUpload}
    </DropZone>
  );
}

Use to trigger the file dialog from an action somewhere else on the page.

import {Stack, Thumbnail, Caption, Card, DropZone} from '@shopify/polaris';
import {NoteMinor} from '@shopify/polaris-icons';
import {useState, useCallback} from 'react';

function DropZoneWithCustomFileDialogExample() {
  const [files, setFiles] = useState([]);
  const [openFileDialog, setOpenFileDialog] = useState(false);

  const handleDropZoneDrop = useCallback(
    (dropFiles, _acceptedFiles, _rejectedFiles) =>
      setFiles((files) => [...files, ...dropFiles]),
    [],
  );
  const toggleOpenFileDialog = useCallback(
    () => setOpenFileDialog((openFileDialog) => !openFileDialog),
    [],
  );

  const validImageTypes = ['image/gif', 'image/jpeg', 'image/png'];

  const uploadedFiles = files.length > 0 && (
    <Stack vertical>
      {files.map((file, index) => (
        <Stack alignment="center" key={index}>
          <Thumbnail
            size="small"
            alt={file.name}
            source={
              validImageTypes.indexOf(file.type) > -1
                ? window.URL.createObjectURL(file)
                : NoteMinor
            }
          />
          <div>
            {file.name} <Caption>{file.size} bytes</Caption>
          </div>
        </Stack>
      ))}
    </Stack>
  );

  return (
    <Card
      sectioned
      title="Product Images"
      actions={[
        {
          content: 'Upload Image',
          onAction: toggleOpenFileDialog,
        },
      ]}
    >
      <DropZone
        openFileDialog={openFileDialog}
        onDrop={handleDropZoneDrop}
        onFileDialogClose={toggleOpenFileDialog}
      >
        {uploadedFiles}
      </DropZone>
    </Card>
  );
}

Props

Want to help make this feature better? Please share your feedback.

  • onFileDialogClose

    Required
    () => void

    Callback triggered when the file dialog is canceled

  • onDragLeave

    Required
    () => void

    Callback triggered when one or more files left the drag area

  • onDragEnter

    Required
    () => void

    Callback triggered when one or more files entered the drag area

  • onDragOver

    Required
    () => void

    Callback triggered when one or more files are dragging over the drag area

  • onDropRejected

    Required
    (rejectedFiles: File[]) => void

    Callback triggered when at least one of the files dropped was rejected

  • onDropAccepted

    Required
    (acceptedFiles: File[]) => void

    Callback triggered when at least one of the files dropped was accepted

  • onDrop

    Required
    (files: File[], acceptedFiles: File[], rejectedFiles: File[]) => void

    Callback triggered on any file drop

  • onClick

    Required
    (event: React.MouseEvent<HTMLElement>) => void

    Callback triggered on click

  • customValidator

    Required
    (file: File) => boolean

    Adds custom validations

  • label? React.ReactNode

    Label for the file input

  • labelAction? Action

    Adds an action to the label

  • labelHidden? boolean

    Visually hide the label

  • id? string

    ID for file input

  • accept? string

    Allowed file types

  • type? DropZoneFileType

    Whether is a file or an image

  • active? boolean

    Sets an active state

  • error? boolean

    Sets an error state

  • outline? boolean

    Displays an outline border

  • overlay? boolean

    Displays an overlay on hover

  • overlayText? string

    Text that appears in the overlay

  • errorOverlayText? string

    Text that appears in the overlay when set in error state

  • allowMultiple? boolean

    Allows multiple files to be uploaded at once

  • disabled? boolean

    Sets a disabled state

  • children? any

    The child elements to render in the dropzone.

  • dropOnPage? boolean

    Allows a file to be dropped anywhere on the page

  • openFileDialog? boolean

    Sets the default file dialog state

  • variableHeight? boolean

    Allows child content to adjust height

Best practices

Drop zone

Drop zones should:

  • Inform merchants when the file(s) can’t be uploaded:
    • When possible, use validation errors on drag to detect and explain things like file size limits or file types accepted.
    • Use the banner component with a critical status to communicate errors that happen on the server.
  • Provide feedback once the file(s) have been dropped and uploading begins.
  • For convenience, allow files to be dropped anywhere on the page by enabling dropOnPage.
  • Provide a file upload button to allow merchants to select files for upload in a traditional way. Do this by using the DropZone.FileUpload subcomponent.

Validation errors

The drop zone component validates file type by default. File types you wish to accept can be defined by editing the accept property. This component also accepts custom validations using the customValidator property. When validation fails, the component sets itself to error mode.


Content guidelines

Client-side validation error messages

Client-side validation errors give instant feedback.

Validation error messages should be:

  • Explicit: help merchants understand why their file can’t be uploaded and what they should change to successfully upload their file
  • In sentence case: capitalize only the first word in the message
  • Concise: use simple, clear language that can be read at a glance. For example:

File size must be less than 20MB

File type must be .gif, .jpg, .png or .svg

Server-side upload error messages

Server-side upload errors give feedback after file submission.

Upload error messages should:

  • Be displayed as a banner with a critical status
  • Show the name of the file(s) that were not uploaded successfully
  • Describe why the file(s) couldn’t be uploaded and what merchants should change to upload their file successfully, as seen below
Example
The following images couldn’t be uploaded:

* “keep-it-real.png” is too large. Try a file size less than 20MB.
* “realer-than-real.zip” is not supported. File type must be .gif, .jpg, .png or .svg.
* “so-so-real.png” was interrupted due to weak network connection, [retry upload](#)

Drop zone file upload

Use file upload with the drop zone component to let merchants select files for upload in a traditional way.

File upload properties

PropTypeDescriptionDefault
actionTitlestringString that appears in file upload'Add file'
actionHintstringString that appears in file upload'or drop files to upload'


Accessibility

The drop zone component builds on the native HTML <input type="upload"> element. It includes a visual<button> as well as a drag and drop area that can receive keyboard focus.

Keyboard support

To upload a file with the keyboard, merchants can interact with the drag-and-drop region.

  • To give the input keyboard focus, use the tab key (or shift + tab when tabbing backwards)
  • To activate the input, use the enter/return or space keys
    đź‘‹

    We've made some improvements to our website to help you build more efficiently with Polaris.