upload area

An upload area is an alternative for the input file upload button. The upload area let the user choose one or more files from their device storage - either via the browse link (that works similar to the input file upload), or via drag and drop into the upload area. These selected files can be uploaded to a server through form submission or be manipulated using the Javascript File API.

The variant Upload area with items shows all states, a file item can have: upload, uploading, uploaded and error.

When selecting / drag and drop files, a custom event called filesAdded is dispatched to the input element. To handle the files, you have to add an event listener to the input element for the event filesAdded.

The demonstrator Upload area demonstrator with custom event example is showing an example and logs the added files to the console.


component variations

default

Drag and drop files here or to upload.
<div class="m-upload-area">
  <div class="m-upload-area__field">
    <div class="m-upload-area__description">
      <i class="a-icon boschicon-bosch-ic-upload"></i>
      Drag and drop files here or
      <input
        id="upload-input-1"
        class="m-upload-area__input"
        name="upload-input"
        type="file"
        multiple=""
      />
      <label for="upload-input-1">browse</label>
      to upload.
    </div>
  </div>
  <div class="m-upload-area__items"></div>
</div>

Upload area with global error

Drag and drop files here or to upload.
<div class="m-upload-area">
  <div class="m-upload-area__field">
    <div class="m-upload-area__description">
      <i class="a-icon boschicon-bosch-ic-upload"></i>
      Drag and drop files here or
      <input
        id="upload-input-2"
        class="m-upload-area__input"
        name="upload-input"
        type="file"
        multiple=""
      />
      <label for="upload-input-2">browse</label>
      to upload.
    </div>
  </div>
  <div class="a-notification a-notification--text -error" role="alert">
    <i class="a-icon ui-ic-alert-error"></i>
    <div id="notification-label-id-text-error" class="a-notification__content">
      This is an optional global upload error notification.
    </div>
  </div>
  <div class="m-upload-area__items"></div>
</div>

Upload area with items

Drag and drop files here or to upload.
ready-to-upload-file.jpg
32.4 MB
uploading-file.jpg
16.8 MB
25%
uploaded-file.jpg
13.1 MB
upload-failed-file.jpg
3.6 MB
<div class="m-upload-area">
  <div class="m-upload-area__field">
    <div class="m-upload-area__description">
      <i class="a-icon boschicon-bosch-ic-upload"></i>
      Drag and drop files here or
      <input
        id="upload-input-3"
        class="m-upload-area__input"
        name="upload-input"
        type="file"
        multiple=""
      />
      <label for="upload-input-3">browse</label>
      to upload.
    </div>
  </div>
  <div class="m-upload-area__items">
    <div class="m-upload-area__item">
      <div class="m-upload-area__item-details">
        <div class="m-upload-area__item-description">
          <i class="a-icon boschicon-bosch-ic-document-plain"></i>
          <div>
            <strong class="-size-m">ready-to-upload-file.jpg</strong>
            <br />
            <span class="-size-s">32.4 MB</span>
          </div>
        </div>
        <div class="m-upload-area__item-cta">
          <button type="button" class="a-button a-button--integrated">
            <i class="a-icon a-button__icon boschicon-bosch-ic-upload"></i>
            <span class="a-button__label">Upload</span>
          </button>
          <button
            type="button"
            class="a-button a-button--integrated -without-label"
            aria-label="delete"
          >
            <i class="a-icon a-button__icon boschicon-bosch-ic-delete"></i>
          </button>
        </div>
      </div>
    </div>
    <div class="m-upload-area__item">
      <div class="m-upload-area__item-details">
        <div class="m-upload-area__item-description">
          <i class="a-icon boschicon-bosch-ic-document-plain"></i>
          <div>
            <strong class="-size-m">uploading-file.jpg</strong>
            <br />
            <span class="-size-s">16.8 MB</span>
          </div>
        </div>
        <div class="m-upload-area__item-cta">
          <button
            type="button"
            class="a-button a-button--integrated -without-label"
            aria-label="delete"
          >
            <i class="a-icon a-button__icon boschicon-bosch-ic-delete"></i>
          </button>
        </div>
      </div>
      <div class="m-upload-area__progress">
        <div class="a-progress-indicator-container">
          <progress
            class="a-progress-indicator -determinate"
            value="25"
            max="100"
          ></progress>
          <div class="a-progress-indicator__inner-bar"></div>
        </div>
        <div class="-size-s m-upload-area__progress-percentage">25%</div>
      </div>
    </div>
    <div class="m-upload-area__item">
      <div class="m-upload-area__item-details">
        <div class="m-upload-area__item-description">
          <i class="a-icon boschicon-bosch-ic-document-plain"></i>
          <div>
            <strong class="-size-m">uploaded-file.jpg</strong>
            <br />
            <span class="-size-s">13.1 MB</span>
          </div>
        </div>
        <div class="m-upload-area__item-cta">
          <button
            type="button"
            class="a-button a-button--integrated -without-label"
            aria-label="delete"
          >
            <i class="a-icon a-button__icon boschicon-bosch-ic-delete"></i>
          </button>
        </div>
      </div>
    </div>
    <div class="m-upload-area__item">
      <div class="m-upload-area__item-details">
        <div class="m-upload-area__item-description">
          <i class="a-icon boschicon-bosch-ic-document-plain"></i>
          <div>
            <strong class="-size-m">upload-failed-file.jpg</strong>
            <br />
            <span class="-size-s">3.6 MB</span>
          </div>
        </div>
        <div class="m-upload-area__item-cta">
          <button type="button" class="a-button a-button--integrated">
            <i class="a-icon a-button__icon boschicon-bosch-ic-reset"></i>
            <span class="a-button__label">Try again</span>
          </button>
          <button
            type="button"
            class="a-button a-button--integrated -without-label"
            aria-label="delete"
          >
            <i class="a-icon a-button__icon boschicon-bosch-ic-delete"></i>
          </button>
        </div>
      </div>
      <div class="a-notification a-notification--text -error" role="alert">
        <i class="a-icon ui-ic-alert-error"></i>
        <div
          id="notification-label-id-text-error"
          class="a-notification__content"
        >
          File failed to load. Please try again!
        </div>
      </div>
    </div>
    <div class="m-upload-area__cta">
      <button type="button" class="a-button a-button--secondary">
        <i class="a-icon a-button__icon boschicon-bosch-ic-upload"></i>
        <span class="a-button__label">Upload all</span>
      </button>
      <button type="button" class="a-button a-button--tertiary">
        <i class="a-icon a-button__icon boschicon-bosch-ic-delete"></i>
        <span class="a-button__label">Remove all</span>
      </button>
    </div>
  </div>
</div>

Upload area demonstrator with custom event example

Drag and drop files here or to upload.
<script>
  window.addEventListener("load", (event) => {
    const uploadInput = document.getElementById('upload-input-4');
    uploadInput.addEventListener('filesAdded', (e) => {
      console.log('files added', e.detail.files);
    });
  });
</script>
<div class="m-upload-area">
  <div class="m-upload-area__field">
    <div class="m-upload-area__description">
      <i class="a-icon boschicon-bosch-ic-upload"></i>
      Drag and drop files here or
      <input
        id="upload-input-4"
        class="m-upload-area__input"
        name="upload-input"
        type="file"
        multiple=""
      />
      <label for="upload-input-4">browse</label>
      to upload.
    </div>
  </div>
  <div class="m-upload-area__items"></div>
</div>

additional content

styles SCSS

.m-upload-area {
  color: var(--plain__enabled__front__default);

  &__field {
    height: 240px;
    background-color: var(--neutral__enabled__fill__default);
    background-image: linear-gradient(to right, var(--neutral__enabled__front__default) 50%, transparent 50%), linear-gradient(to right, var(--neutral__enabled__front__default) 50%, transparent 50%), linear-gradient(to bottom, var(--neutral__enabled__front__default) 50%, transparent 50%), linear-gradient(to bottom, var(--neutral__enabled__front__default) 50%, transparent 50%);
    background-position: left top, left bottom, left top, right top;
    background-repeat: repeat-x, repeat-x, repeat-y, repeat-y;
    background-size: 2rem 1px, 2rem 1px, 1px 2rem, 1px 2rem;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;

    + .a-notification {
      padding-block: .75rem;
    }
  }

  &__description {
    max-width: 200px;
    text-align: center;

    .a-icon {
      display: block;
      font-size: 3rem;
      margin-bottom: 0.5rem;
    }

    label {
      cursor: pointer;
      text-decoration: underline;
      font-size: 1rem;
    }
  }

  &__input {
    width: 0;
    height: 0;
    opacity: 0;

    &:focus-visible {
      & + label {
        @include focus-outside;

        &::after {
          outline-color: var(--neutral__enabled__fill__default);
        }
      }
    }
  }

  &__items {
    margin-top: 1rem;
  }

  &__item {
    border-top: 1px solid var(--small__enabled__fill__default);
    padding-block: 1rem;

    &:first-child {
      border-top: none;
    }
  }

  &__item-details {
    display: flex;
    justify-content: space-between;

    + .a-notification {
      margin-top: .25rem;
    }
  }

  &__item-description {
    display: flex;
    flex-grow: 1;
    align-items: center;
    gap: 1rem;

    .a-icon {
      margin: .75rem;
    }
  }

  &__item-cta {
    display: flex;
    justify-content: flex-end;
    align-items: center;
  }

  &__progress {
    display: flex;
    align-items: center;

    .a-progress-indicator-container {
      flex-grow: 1;
    }
  }

  &__progress-percentage {
    width: 3rem;
    text-align: center;
  }

  &__cta {
    margin-top: 1rem;
    display: flex;
    justify-content: flex-end;
    gap: .75rem;
  }
}