FileDropField
FileDropField is an input that accepts drag-and-drop files such as images or PDFs.

Item | Name | Description |
---|---|---|
A | Drop zone | A <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> |
D | Text | A <span> used to provide additional direction |


Item | Name | Description |
---|---|---|
A | Container | The <FileListItem /> is rendered as a <li> |
B | Content slot | Slot 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 /> |

A basic FileDropField
that allows one file using Formik and Yup.
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
.
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>
);
};
Name | Type | Default | Description |
---|---|---|---|
id | string | The ID of the input element. | |
invalid | boolean | If true , the field will be shown in an invalid state. | |
label Required | string | Label associated with the input . | |
optionalText | string | Text used to indicate that a field is not required. | |
helperText | ReactNode | A brief description or hint for the input ; can be used to link to an external resource. | |
validationText | ReactNode | Validation message associated with the input . | |
children | ReactNode | The content of the component. | |
before | ReactNode | Content shown before the children. | |
after | ReactNode | Content shown after children. | |
dropHelperText | string | or drop files | Text guiding users to drop files onto the target area. |
onChange | func | Callback triggered when a user makes a new selection. |
Name | Type | Default | Description |
---|---|---|---|
readOnly | boolean | If 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 | ReactNode | The content of the FileList. |
Name | Type | Default | Description |
---|---|---|---|
aria-label | string | The accessible name for this item. Most commonly the file name. | |
invalid | boolean | If true this item will be marked as invalid. | |
fileTypeIcon | ReactNode | Icon used to indicate the type of the File being uploaded. | |
children | ReactNode | The content of the FileListItem. | |
onRemove | ReactNode | Callback 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 Library with occasional help from Jest.
Single file upload example
render(<FormikinitialValues={{documents: [new File(['HannahArdent2022W2'], 'HannahArdent2022W2.pdf', {type: 'application/pdf',}),],}}validationSchema={singleSchema}onSubmit={values => alert(JSON.stringify(values))}><Form><FileDropFieldlabel="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(<FormikinitialValues={{ documents: new Array<File>() }}validationSchema={multipleSchema}onSubmit={values => alert(JSON.stringify(values))}><Form><FileDropFieldlabel="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');