File Uploader

A File Uploader is a design system component that allows users to select and upload files from their device to an application or website. It typically features a drag-and-drop area or a button for browsing files and may include progress indicators and error handling.
This component streamlines the process of submitting files, enhancing user experience by making it straightforward and efficient.

Live code

Storybook

Storybook failed to load. Please connect to the VPN to access.

Usage

FileUploader

The FileUploader implementation is used to select files either by selecting files from the device itself, or by drag and drop if the device supports it. React package extends web package.

Usage

Basic


                                                
                                                const { fileQueue, addToQueue, clearQueue, onDismiss } = useFileQueue();
                                                
                                                <FileUploader
                                                  id="file-uploader-example"
                                                  onDismiss={onDismiss}
                                                  fileQueue={fileQueue}
                                                  addToQueue={addToQueue}
                                                  clearQueue={clearQueue}
                                                >
                                                  <FileUploaderInput
                                                    id="file-uploader-example-input"
                                                    name="attachments"
                                                    label="Label"
                                                    linkText="Upload your file(s)"
                                                    labelText="or drag and drop here"
                                                    helperText="Max file size is 10 MB"
                                                  />
                                                  <FileUploaderList
                                                    id="file-uploader-example-list"
                                                    label="Attachments"
                                                    inputName="attachments"
                                                    attachmentComponent={(props) => <FileUploaderAttachment key={props.id} {...props} />}
                                                  />
                                                </FileUploader>;

Multiple and Queue Limit


                                                
                                                const { fileQueue, addToQueue, clearQueue, onDismiss } = useFileQueue();
                                                
                                                <FileUploader
                                                  id="file-uploader-example"
                                                  onDismiss={onDismiss}
                                                  fileQueue={fileQueue}
                                                  addToQueue={addToQueue}
                                                  clearQueue={clearQueue}
                                                >
                                                  <FileUploaderInput
                                                    id="file-uploader-example-input"
                                                    name="attachments"
                                                    label="Label"
                                                    linkText="Upload your file(s)"
                                                    labelText="or drag and drop here"
                                                    helperText="Max file size is 10 MB"
                                                    validationText="Validation message"
                                                    maxUploadedFiles={5}
                                                    isMultiple
                                                  />
                                                  <FileUploaderList
                                                    id="file-uploader-example-list"
                                                    label="Attachments"
                                                    inputName="attachments"
                                                    attachmentComponent={(props) => <FileUploaderAttachment key={props.id} {...props} />}
                                                  />
                                                </FileUploader>;

List with Image Previews


                                                
                                                <FileUploaderList
                                                  id="file-uploader-example-list"
                                                  label="Attachments"
                                                  inputName="attachments"
                                                  attachmentComponent={attachmentComponent}
                                                  hasImagePreview
                                                />

Editable Attachment


                                                
                                                <FileUploaderAttachment key={id} id={id} onEdit={(event, file) => console.log(event, file)} {...props} />

Validation State


                                                
                                                <FileUploader>
                                                  <FileUploaderInput isRequired validationState="success" validationText="Validation message" />
                                                  <FileUploaderList />
                                                </FileUploader>
                                                <FileUploader>
                                                  <FileUploaderInput isRequired validationState="success" validationText={["Validation message", "Second validation message"]} />
                                                  <FileUploaderList />
                                                </FileUploader>

Input Behavior When the Queue Is Filled

FileUploaderInput will disappear or disable after reaching the limit for files in the queue.


                                                
                                                <FileUploader>
                                                  <FileUploaderInput
                                                    isRequired
                                                    validationState="success"
                                                    validationText="Validation message"
                                                    maxUploadedFiles={3}
                                                    queueLimitBehavior="hide"
                                                  />
                                                  <FileUploaderList />
                                                </FileUploader>

Error Callback


                                                
                                                <FileUploader>
                                                  <FileUploaderInput onError={(error) => console.error('My error log', error)} />
                                                  <FileUploaderList />
                                                </FileUploader>

Uncontrolled


                                                
                                                <UncontrolledFileUploader
                                                  attachmentComponent={(props) => <FileUploaderAttachment key={props.id} {...props} />}
                                                  id="file-uploader-uncontrolled"
                                                  inputId="file-uploader-uncontrolled-input"
                                                  inputName="attachments"
                                                  inputProps={{
                                                    accept: '*',
                                                  }}
                                                  listId="file-uploader-uncontrolled-list"
                                                  listProps={{
                                                    label: 'Attachments',
                                                  }}
                                                  onInputError={(error) => console.error('My error log', error)}
                                                />

Controlled Example with Form Submit


                                                
                                                const [submitting, setSubmitting] = useState < boolean > false;
                                                const [submitted, setSubmitted] = useState < boolean > false;
                                                const [validationText, setValidationText] = (useState < string) | (undefined > undefined);
                                                const [validationState, setValidationState] = (useState < ValidationState) | (undefined > undefined);
                                                
                                                const { fileQueue, addToQueue, clearQueue, onDismiss } = useFileQueue();
                                                
                                                const submitHandler = (event: FormEvent<HTMLFormElement>) => {
                                                  event.preventDefault();
                                                  const master = new Map(fileQueue);
                                                
                                                  setSubmitting(true);
                                                
                                                  // Callback after submit
                                                  setSubmitting(false);
                                                  setSubmitted(true);
                                                  clearQueue();
                                                };
                                                
                                                const errorHandler = (error: string | Error) => {
                                                  setValidationState('error');
                                                  setValidationText(String(error));
                                                
                                                  setTimeout(() => {
                                                    setValidationState(undefined);
                                                    setValidationText(undefined);
                                                  }, 5000);
                                                };
                                                
                                                const resetStateHandler = () => {
                                                  setValidationState(undefined);
                                                  setValidationText(undefined);
                                                  setSubmitted(false);
                                                };
                                                
                                                <form onSubmit={submitHandler}>
                                                  <FileUploader
                                                    id="file-uploader-form-example"
                                                    onDismiss={onDismiss}
                                                    fileQueue={fileQueue}
                                                    addToQueue={addToQueue}
                                                    clearQueue={clearQueue}
                                                  >
                                                    <FileUploaderInput
                                                      id="file-uploader-form-example-input"
                                                      name="attachments"
                                                      label="Label"
                                                      linkText="Upload your file(s)"
                                                      labelText="or drag and drop here"
                                                      helperText="Max file size is 10 MB"
                                                      validationText={validationText}
                                                      validationState={validationState}
                                                      maxUploadedFiles={2}
                                                      onError={errorHandler}
                                                      isMultiple
                                                    />
                                                    <FileUploaderList
                                                      id="file-uploader-form-example-list"
                                                      label="Attachments"
                                                      inputName="attachments"
                                                      attachmentComponent={(props) => <FileUploaderAttachment key={props.id} {...props} />}
                                                    />
                                                  </FileUploader>
                                                  <div style={{ paddingTop: '1rem' }}>
                                                    <Button type="submit" color="primary" isDisabled={submitting || submitted}>
                                                      Submit form
                                                    </Button>
                                                    {submitted && (
                                                      <Button color="secondary" onClick={resetStateHandler} UNSAFE_style={{ marginLeft: '.5rem' }}>
                                                        Reset state
                                                      </Button>
                                                    )}
                                                  </div>
                                                </form>;

Uncontrolled Example with Form Submit


                                                
                                                const [submitting, setSubmitting] = useState < boolean > false;
                                                const [submitted, setSubmitted] = useState < boolean > false;
                                                const [validationText, setValidationText] = (useState < string) | (undefined > undefined);
                                                const [validationState, setValidationState] = (useState < ValidationState) | (undefined > undefined);
                                                const [queue, setQueue] = useState < FileQueueMapType > new Map();
                                                
                                                const attachmentComponent = ({ id, ...props }: SpiritFileUploaderAttachmentProps) => (
                                                  <FileUploaderAttachment key={id} id={id} {...props} />
                                                );
                                                
                                                const changeHandler = (fileQueue: FileQueueMapType) => {
                                                  setQueue(fileQueue);
                                                };
                                                
                                                const submitHandler = (event: FormEvent<HTMLFormElement>) => {
                                                  event.preventDefault();
                                                  const master = new Map(queue);
                                                
                                                  setSubmitting(true);
                                                
                                                  // Callback after submit
                                                  setSubmitting(false);
                                                  setSubmitted(true);
                                                
                                                  queue.clear();
                                                  setQueue(new Map(queue));
                                                };
                                                
                                                const errorHandler = (error: string | Error) => {
                                                  setValidationState('error');
                                                  setValidationText(String(error));
                                                
                                                  setTimeout(() => {
                                                    setValidationState(undefined);
                                                    setValidationText(undefined);
                                                  }, 5000);
                                                };
                                                
                                                const resetStateHandler = () => {
                                                  setValidationState(undefined);
                                                  setValidationText(undefined);
                                                  setSubmitted(false);
                                                };
                                                
                                                <form onSubmit={submitHandler}>
                                                  <UncontrolledFileUploader
                                                    attachmentComponent={attachmentComponent}
                                                    id="file-uploader-uncontrolled"
                                                    inputId="fileUploaderUncontrolledInput"
                                                    inputName="attachments"
                                                    inputLabel="Input label"
                                                    listId="file-uploader-uncontrolled-list"
                                                    linkText="Upload your file(s)"
                                                    labelText="or drag and drop here"
                                                    helperText="Max file size is 10 MB"
                                                    onChange={changeHandler}
                                                    onInputError={errorHandler}
                                                    validationState={validationState}
                                                    validationText={validationText}
                                                    isMultiple
                                                  />
                                                  <div style={{ paddingTop: '1rem' }}>
                                                    <Button type="submit" color="primary" isDisabled={submitting || submitted}>
                                                      Submit form to show data
                                                    </Button>
                                                    {submitted && (
                                                      <Button color="secondary" onClick={resetStateHandler} UNSAFE_style={{ marginLeft: '.5rem' }}>
                                                        Reset state
                                                      </Button>
                                                    )}
                                                  </div>
                                                </form>;

Passing Additional Metadata

When you need to send additional data along with the image you can do it with the meta argument on addToQueue and updateQueue callbacks. If any data in meta option will be present, the FileUploader adds an additional hidden input with JSON stringified data to the form. The identification of this input (name) will be the name of the file. Thus you can pass to the server any additional text data you need.


                                                
                                                const customAddToQueue = (key: string, file: File) => {
                                                  // passing additional data using the `meta` argument
                                                  return addToQueue(key, file, { fileDescription: 'custom file description' });
                                                };
                                                
                                                const customUpdate = (_event: MouseEvent, file: File) => {
                                                  return updateQueue(file.name, file, { fileDescription: 'changing the custom description' });
                                                };
                                                
                                                // …
                                                <FileUploader
                                                  addToQueue={customAddToQueue}
                                                  // …
                                                  updateQueue={updateQueue}
                                                >
                                                  // …
                                                </FileUploader>;
                                                // …

Updating Image Preview with Cropped Image

When you are using FileUploader with some kind of image cropper you want to also update the image preview on FileUploaderAttachment when image changes. You can do this by passing a specific object in shape of coordinates ({ x: number, y: number, cropWidth: number, cropHeight: number, originalWidth: number, originalHeight: number }) to the meta argument. Then the coordinates will be applied to the preview image in the attachment.


                                                
                                                // …
                                                const customUpdate = (_event: MouseEvent, file: File) => {
                                                  const meta = { x: 30, y: 30, cropWidth: 150, cropHeight: 150, originalWidth: 560, originalHeight: 330 };
                                                
                                                  return updateQueue(file.name, file, meta);
                                                };
                                                // …

FileUploader Props

Name Type Default Required Description
addToQueue (key: string, file: File, meta?: FileMetadata) => FileQueueMapType Callback to add an item to the queue
clearQueue () => void Callback to clear the queue
errorMessages.errorFileDuplicity string Translation for the error message: Duplicate file in queue
errorMessages.errorMaxFileSize string Translation for the error message: Maximum file size
errorMessages.errorMaxUploadedFiles string Translation for the error message: Maximum number of uploaded files
fileQueue FileQueueMapType Queue of items to upload
findInQueue (key: string) => FileQueueMapType A callback to find a particular item in the queue
id string FileUploader id
isFluid bool When the field is supposed to be fluid
onDismiss (key: string) => FileQueueMapType A callback to delete a particular item from the queue
updateQueue (key: string, file: File, meta?: FileMetadata) => FileQueueMapType A callback to update a particular item in the queue

The rest of the properties are created from the default <div> element. More about the element

On top of the API options, the components accept additional attributes. If you need more control over the styling of a component, you can use style props and escape hatches.

FileUploaderInput Props

Name Type Default Required Description
accept string The accept attribute takes as its value a comma-separated list of one or more file types, or unique file type specifiers, describing which file types to allow.
dropZoneRef MutableRefObject<HTMLDivElement> Drop zone element reference
helperText string Custom helper text
iconName string upload Icon used in the drop zone
id string FileUploaderInput id
inputRef MutableRefObject<HTMLInputElement> Input element reference
isDisabled bool Whether is field disabled
isLabelHidden bool Whether is input label hidden
isMultiple bool When multiple files can be selected at once
isRequired bool Whether is field marked as required
label string Field label
labelText string Label for input in Drop zone
linkText string Link text in input in Drop zone
maxFileSize number 1000000 The maximum size of the uploaded file in bytes. Learn how file sizes are calculated.
maxUploadedFiles number 10 Maximum file upload queue size
name string Field name, will be used for each attachment in the queue
onError FileUploaderErrorCallbackType Callback on error condition
queueLimitBehavior [hide | disable | none] none Input behavior when the file queue is filled
validationState ValidationState Validation state
validationText [string | string[]] Validation status text

The rest of the properties are created from the default <input> element. More about the element

On top of the API options, the components accept additional attributes. If you need more control over the styling of a component, you can use style props and escape hatches.

⚠️ We don't use the required attribute on the input element. This is because it triggers the browser's default validation, which can block form submission. Instead, the FileUploaderInput component is used to open the system file dialog, and the component itself manages the file(s). Please note, the validation for required files is not automatically handled. Developers need to implement this validation independently. This approach provides more flexibility and customization to meet specific validation requirements.

FileUploaderList Props

Name Type Default Required Description
attachmentComponent string A component for rendering a single attachment
hasImagePreview bool false Show image preview in the list
id string FileUploaderList id
inputName string The name of the input field
label string Label for the list

The rest of the properties are created from the default <ul> element. More about the element

On top of the API options, the components accept additional attributes. If you need more control over the styling of a component, you can use style props and escape hatches.

FileUploaderAttachment Props

Name Type Default Required Description
editText string Edit Edit button label
file File Attachment file object
hasImagePreview bool false Show image preview
iconName string file Icon shown along the file
id string FileUploaderAttachment id
imageObjectFit [cover | contain] cover Defines FileUploaderAttachment image fit in container
label string File name
name string Input field name
onDismiss (key: string) => FileQueueMapType Callback to delete an item from the queue
onEdit (event: Event, file: File) => void Show and add function to edit button
onError FileUploaderErrorCallbackType Callback on error condition
removeText string Remove Remove button label

The rest of the properties are created from the default <li> element. More about the element

On top of the API options, the components accept additional attributes. If you need more control over the styling of a component, you can use style props and escape hatches.

AttachmentActionButton Props

Name Type Default Required Description
onClick MouseEventHandler<HTMLButtonElement> Button click handler

The rest of the properties are created from the default <button> element. More about the element

On top of the API options, the components accept additional attributes. If you need more control over the styling of a component, you can use style props and escape hatches.

AttachmentDismissButton Props

Name Type Default Required Description
onClick MouseEventHandler<HTMLButtonElement> Button click handler

The rest of the properties are created from the default <button> element. More about the element

On top of the API options, the components accept additional attributes. If you need more control over the styling of a component, you can use style props and escape hatches.

UncontrolledFileUploader Props

The uncontrolled variant contains the composition <FileUploaderInput> and <FileUploaderList> and a collection of props selected above for easier implementation. Other props are passed via inputProps and listProps. UncontrolledFileUploaderBaseProps extends FileUploaderBaseProps.

Name Type Default Required Description
attachmentComponent string A component for rendering a single attachment
helperText string Custom helper text
iconName string upload Icon used in the UncontrolledFileUploader drop zone
id string UncontrolledFileUploader id
inputId string FileUploaderInput id
inputLabel string FileUploaderInput label
inputName string FileUploaderInput name
inputProps Partial<FileUploaderInputBaseProps> Rest of FileUploaderInput props
isDisabled bool Whether is field disabled
isFluid bool When the field is supposed to be fluid
isLabelHidden bool Whether is input label hidden
isMultiple bool When multiple files can be selected at once
isRequired bool Whether is field marked as required
labelText string Label for input in Drop zone
linkText string Link text in input in Drop zone
listId string FileUploaderList id
listProps Partial<FileUploaderListBaseProps> Rest of FileUploaderList props
maxFileSize number 1000000 The maximum size of the uploaded file in bytes. Learn how file sizes are calculated.
maxUploadedFiles number 10 Maximum file upload queue size
onChange (fileQueue: FileQueueMapType) => void Callback on change in fileQueue
onInputError FileUploaderErrorCallbackType Callback on error condition
queueLimitBehavior [hide | disable | none] none Input behavior when the file queue is filled
validationState ValidationState Validation state
validationText [string | string[]] Validation status text

On top of the API options, the components accept additional attributes. If you need more control over the styling of a component, you can use style props and escape hatches.

For detailed information see FileUploader component.

Understanding File Size in Bytes

File size is measured in bytes, where larger units such as kibibytes (KiB) or mebibytes (MiB) are derived as follows:

  • 1 KiB (kibibyte) = 1,024 bytes
  • 1 MiB (mebibyte) = 1,024 KiB = 1,024 × 1,024 bytes = 1,048,576 bytes
  • 10 MiB (mebibytes) = 10 × 1,048,576 bytes = 10,485,760 bytes

When setting the maxFileSize parameter, specify the maximum size of an uploaded file in bytes.

For example: To limit files to 1 MiB, set maxFileSize to 1048576. To limit files to 10 MiB, set maxFileSize to 10485760.

Kilo/Mega vs Kibi/Mebi

In computing, file sizes can be measured using two different standards. The decimal (base-10) and binary (base-2) systems. This can cause confusion when terms like kilobyte and kibibyte are used interchangeably.

Kilobyte (kB): Based on the decimal system, 1 kilobyte equals 1,000 bytes. This is commonly used in contexts like hard drive capacities, where manufacturers use the decimal standard.

Kibibyte (KiB): Based on the binary system, 1 kibibyte equals 1,024 bytes. This is often used in computer memory and file size calculations.

The same distinction applies to larger units like megabytes (MB) and mebibytes (MiB):

  • 1 MB = 1,000,000 bytes (decimal)
  • 1 MiB = 1,048,576 bytes (binary)

For precise calculations, especially when using parameters like maxFileSize, it is recommended to use the binary standard and specify values in bytes based on the binary system.

For more details about binary prefixes and file size standards, check out this Wikipedia article on binary prefixes.

PHP & Next.js Configurations

In some cases, you can set file sizes using shorthand notations. However, these values are still interpreted as binary sizes internally.

For example, in PHP you can use the shorthand 1M for the upload_max_filesize, which corresponds to 1,048,576 bytes (1 MiB):


                                                
                                                upload_max_filesize=1M

Similarly, in Next.js, you can specify file size limits using a shorthand like 1mb. This will also be interpreted as 1,048,576 bytes (1 MiB):


                                                
                                                module.exports = {
                                                  experimental: {
                                                    serverActions: {
                                                      bodySizeLimit: '1mb',
                                                    },
                                                  },
                                                };

Icons

This component uses the Icon component internally. To ensure correct rendering, please refer to the Icon component documentation for setup instructions.