Skip to content

FileDropField

FileDropField is an input that accepts drag-and-drop files such as images or PDFs.
Figma logo
Breakdown of a file input pointing out its input, drop zone, plus icon, and drop helper text. See the following table for more information.
Anatomy of a FileInput
Anatomy of the file input component
ItemNameDescription
ADrop zoneA <div> that wraps the rest of the content.
B<Plus />Icon used to help convey that the user can upload a file
C<input />An <input type=“file”> styled as a <Button>
DTextA <span> used to provide additional direction
Breakdown of the file input showing its dimensions
Blueprint of a FileInput
Breakdown of the FileListItem pointing out its container, content slot, and remove button. See the following table for more information.
Anatomy of a FileListItem
Anatomy of the FileListItem component
ItemNameDescription
AContainerThe <FileListItem /> is rendered as a <li>
BContent slotSlot used to render a text span or a <Link /> to preview file and an <Error /> icon when invalid is true.
C<IconButton />An <IconButton /> is used to allow the user to remove a file from the list
D<Close />Icon used for the <IconButton />
Breakdown of the FileListItem showing its dimensions.
Blueprint of a FileListItem

A basic FileDropField that allows one file using Formik and Yup.

Only PDF, Microsoft Word, or similar files of 500mb or less

    Show code
    import { Formik } from 'formik';
    import React from 'react';
    import * as Yup from 'yup';
    
    import { Actions, Flex } from '@gusto/workbench';
    import {
      FileDropField,
      FileList,
      FileListItem,
      Form,
      SubmitButton,
    } from '@gusto/workbench-formik';
    import { Plus } from '@gusto/workbench-icons';
    // Within your application you should be able to directly use window.File and new File()
    import { File } from '@utils';
    
    export const Basic = () => {
      return (
        <Formik
          initialValues={{ document: null as File | null }}
          validationSchema={Yup.object().shape({
            document: Yup.mixed()
              .test({
                name: 'type',
                message:
                  'Invalid W-2 provided. Must be a PDF, Microsft Word document, or similar and 500 MB or less.',
                test: (value: File) => {
                  if (value == null) {
                    return true;
                  }
    
                  const file = value as File;
                  return (
                    file.type.startsWith('application/') && file.size <= 500_000_000
                  );
                },
              })
              .required('W-2 is required'),
          })}
          onSubmit={values => alert(JSON.stringify(values))}
        >
          {({ values }) => {
            return (
              <Form>
                <Flex maxWidth={660} rowGap={5} flexDirection="column">
                  <Flex rowGap={3} flexDirection="column">
                    <FileDropField
                      label="Add W-2s"
                      helperText="Only PDF, Microsoft Word, or similar files of 500mb or less"
                      before={<Plus />}
                      name="document"
                    >
                      Upload files
                    </FileDropField>
                    <FileList name="document">
                      {values && values?.document ? (
                        <FileListItem aria-label={values.document.name}>
                          {values.document.name}
                        </FileListItem>
                      ) : null}
                    </FileList>
                  </Flex>
                  <Actions>
                    <SubmitButton>Submit</SubmitButton>
                  </Actions>
                </Flex>
              </Form>
            );
          }}
        </Formik>
      );
    };
    

    Using Formik and Yup to control multiple file uploads on a FileDropField.

    Only PDF, Microsoft Word, or similar files of 500mb or less

      Show code
      import { Formik } from 'formik';
      import React from 'react';
      import * as Yup from 'yup';
      
      import { Actions, Flex } from '@gusto/workbench';
      import {
        FileDropField,
        FileList,
        FileListItem,
        Form,
        SubmitButton,
      } from '@gusto/workbench-formik';
      import { Plus } from '@gusto/workbench-icons';
      // Within your application you should be able to directly use window.File and new File()
      import { buildNewFile, File } from '@utils';
      
      export const MultipleWithPreview = () => {
        return (
          <Formik
            initialValues={{
              documents: [
                buildNewFile(['HannahArdent2022W2'], 'HannahArdent2022W2.pdf', {
                  type: 'application/pdf',
                }),
                buildNewFile(['IsaiahBerlin2022W2'], 'IsaiahBerlin2022W2.pdf', {
                  type: 'application/pdf',
                }),
              ],
            }}
            validationSchema={Yup.object().shape({
              documents: Yup.array()
                .of(
                  Yup.mixed().test({
                    name: 'type',
                    message:
                      'Invalid W-2 provided. Must be a PDF, Microsft Word document, or similar and 500 MB or less.',
                    test: (value: unknown) => {
                      if (value == null) {
                        return true;
                      }
      
                      const file = value as File;
                      return (
                        file.type.startsWith('application/') &&
                        file.size <= 500_000_000
                      );
                    },
                  }),
                )
                .min(1, 'Choose one W-2'),
            })}
            onSubmit={values => alert(JSON.stringify(values))}
          >
            {({ values }) => {
              return (
                <Form>
                  <Flex maxWidth={660} rowGap={5} flexDirection="column">
                    <Flex rowGap={3} flexDirection="column">
                      <FileDropField
                        label="Add W-2s"
                        helperText="Only PDF, Microsoft Word, or similar files of 500mb or less"
                        before={<Plus />}
                        name="documents"
                        multiple
                      >
                        Upload files
                      </FileDropField>
                      <FileList name="documents">
                        {values?.documents?.map(document =>
                          document ? (
                            <FileListItem aria-label={document.name}>
                              {document.name}
                            </FileListItem>
                          ) : null,
                        )}
                      </FileList>
                    </Flex>
                    <Actions>
                      <SubmitButton>Submit</SubmitButton>
                    </Actions>
                  </Flex>
                </Form>
              );
            }}
          </Formik>
        );
      };
      
      React props
      NameTypeDefaultDescription
      id  stringThe ID of the input element.
      invalid  booleanIf true, the field will be shown in an invalid state.
      label  RequiredstringLabel associated with the input.
      optionalText  stringText used to indicate that a field is not required.
      helperText  ReactNodeA brief description or hint for the input; can be used to link to an external resource.
      validationText  ReactNodeValidation message associated with the input.
      children  ReactNodeThe content of the component.
      before  ReactNodeContent shown before the children.
      after  ReactNodeContent shown after children.
      dropHelperText  stringor drop filesText guiding users to drop files onto the target area.
      onChange  funcCallback triggered when a user makes a new selection.
      React props
      NameTypeDefaultDescription
      readOnly  booleanIf true, this list will be immutable and the remove buttons will be hidden. This can be used in cases where the corresponding input element isdisabled.
      children  ReactNodeThe content of the FileList.
      React props
      NameTypeDefaultDescription
      aria-label  stringThe accessible name for this item. Most commonly the file name.
      invalid  booleanIf true this item will be marked as invalid.
      fileTypeIcon  ReactNodeIcon used to indicate the type of the File being uploaded.
      children  ReactNodeThe content of the FileListItem.
      onRemove  ReactNodeCallback triggered when the remove button is clicked.
      getInvalidDescription  function() => 'Invalid'Callback which can be provided to modify the accessible description of the item when it is in the invalid state.
      • The focus indicators meet the minimum requirements for focus appearance and focus visibility. For more information, see footnote 1,For more information, see footnote 2
      • The drop zone changes to a hover style when a customer drags and holds a file over it to give them visual feedback and confirmation that that is the right place to drop the file. For more information, see footnote 3
      • The touch-target areas for the input and options meet the minimum requirement for target size. For more information, see footnote 4
      • Use of the accept attribute is discouraged because it isn’t obvious what the restriction is and can be too narrow. Instead allow customers to upload as flexibly as possible and have the backend determine whether or not this is ok. For example, validate that the mime type starts with image rather than image/png, image/jpeg, ... . This enables cases such as direct upload from an iOS device live photo HEIF without user confusion and frustration. If you must place these restrictions, do so after the files have been selected so that we can offer more direct feedback to the effect of “Only image files are allowed” or “Image files must be 500mb or less.” For more information, see footnote 5

      The following testing snippet(s) offer suggestions for testing the component using React Testing LibraryExternal link with occasional help from JestExternal link.

      Single file upload example

      render(
      <Formik
      initialValues={{
      documents: [
      new File(['HannahArdent2022W2'], 'HannahArdent2022W2.pdf', {
      type: 'application/pdf',
      }),
      ],
      }}
      validationSchema={singleSchema}
      onSubmit={values => alert(JSON.stringify(values))}
      >
      <Form>
      <FileDropField
      label="Add employee tax documents"
      helperText="Only PDF, Microsoft Word, or similar files of 500mb or less"
      name="documents"
      >
      Upload files
      </FileDropField>
      <FormikFileList name="documents" aria-label="Files" />
      <button type="submit">Submit</button>
      </Form>
      </Formik>,
      );
      const fileInput = screen.getByLabelText('Add employee tax documents') as HTMLInputElement;
      const fileList = screen.getByRole('list', { name: 'Files' });
      expect(fileInput).toBeValid();
      let fileItems = within(fileList).getAllByRole('listitem');
      expect(fileItems).toHaveLength(1);
      expect(fileItems[0]).toHaveAccessibleName('HannahArdent2022W2.pdf');
      await act(async () =>
      userEvent.upload(
      fileInput,
      new File(['EmilyLee2022W2'], 'EmilyLee2022W2.pdf', {
      type: 'application/pdf',
      }),
      ),
      );
      expect(fileInput).toBeValid();
      fileItems = within(fileList).getAllByRole('listitem');
      expect(fileItems).toHaveLength(1);
      expect(fileItems[0]).toHaveAccessibleName('EmilyLee2022W2.pdf');

      Multiple file upload example

      const fileSchema = Yup.mixed()
      .test({
      name: 'type',
      message: invalidFileTypeMessage,
      test: (value: unknown) => {
      if (value == null) {
      return true;
      }
      const file = value as File;
      return file.type.startsWith('application/');
      },
      })
      .test({
      name: 'size',
      message: invalidFileSizeMessage,
      test: (value: unknown) => {
      if (value == null) {
      return true;
      }
      const file = value as File;
      return file.size <= 500_000_000;
      },
      });
      const multipleSchema = Yup.object().shape({
      documents: Yup.array().of(fileSchema).required(requiredMessage),
      });
      render(
      <Formik
      initialValues={{ documents: new Array<File>() }}
      validationSchema={multipleSchema}
      onSubmit={values => alert(JSON.stringify(values))}
      >
      <Form>
      <FileDropField
      label="Add employee tax documents"
      helperText="Only PDF, Microsoft Word, or similar files of 500mb or less"
      name="documents"
      multiple
      >
      Upload files
      </FileDropField>
      <FormikFileList name="documents" aria-label="Files" />
      <button type="submit">Submit</button>
      </Form>
      </Formik>,
      );
      const fileInput = screen.getByLabelText('Add employee tax documents') as HTMLInputElement;
      const fileList = screen.getByRole('list', { name: 'Files' });
      expect(fileList).toBeEmptyDOMElement();
      expect(fileInput).toBeValid();
      await act(async () =>
      userEvent.upload(fileInput, [
      new File(['HannahArdent2022W2'], 'HannahArdent2022W2.pdf', {
      type: 'application/pdf',
      }),
      new File(['IsaiahBerlin2022W2'], 'IsaiahBerlin2022W2.pdf', {
      type: 'application/pdf',
      }),
      ]),
      );
      expect(fileList).toBeValid();
      let fileListItems = within(fileList).getAllByRole('listitem');
      expect(fileListItems).toHaveLength(2);
      expect(fileListItems[0]).toHaveAccessibleName('HannahArdent2022W2.pdf');
      expect(fileListItems[1]).toHaveAccessibleName('IsaiahBerlin2022W2.pdf');
      await act(async () =>
      userEvent.upload(fileInput, [
      new File(['EmilyLee2022W2'], 'EmilyLee2022W2.pdf', {
      type: 'application/pdf',
      }),
      ]),
      );
      expect(fileList).toBeValid();
      fileListItems = within(fileList).getAllByRole('listitem');
      expect(fileListItems).toHaveLength(3);
      expect(fileListItems[0]).toHaveAccessibleName('EmilyLee2022W2.pdf');
      expect(fileListItems[1]).toHaveAccessibleName('HannahArdent2022W2.pdf');
      expect(fileListItems[2]).toHaveAccessibleName('IsaiahBerlin2022W2.pdf');