File Group

Upload a product image

  • Accepted formats: JPG, PNG
  • Maximum upload limit: 5 MB

Upload a product image

  • Accepted formats: JPG, PNG
  • Maximum upload limit: 5 MB
@use 'sprucecss/scss/spruce' as *;

.file-group-container {
  container: file-group-container / inline-size;
}

.file-group {
  $this: &;
  @include generate-variables($form-control, $include: ('border-width', 'border-radius'));
  align-items: center;
  border: config('border-width', $form-control) solid color('border', 'form');
  border-radius: config('border-radius', $form-control);
  display: flex;
  gap: spacer('m');
  overflow: hidden;
  padding: spacer('s');

  @container file-group-container (inline-size < 30rem) {
    flex-direction: column;
  }

  &:has([style*='background-image']) {
    #{$this}__remove {
      display: inline-flex;
    }

    #{$this}__input {
      display: none;
    }
  }

  &__preview {
    align-items: center;
    aspect-ratio: 1;
    background-color: color('primary-50');
    background-position: center;
    background-size: cover;
    border-radius: config('border-radius', $form-control);
    display: flex;
    flex-shrink: 0;
    flex-wrap: wrap;
    inline-size: 9rem;
    justify-content: center;

    @container file-group-container (inline-size < 30rem) {
      aspect-ratio: 16 / 9;
      inline-size: 100%;
    }

    &[style*='background-image'] #{$this}__icon {
      display: none;
    }
  }

  &__body {
    display: flex;
    flex-direction: column;
    gap: spacer('xxs');
    inline-size: 100%;

    > * {
      margin-block: 0;
    }
  }

  &__icon {
    --size: 2rem;
    block-size: var(--size);
    color: color('primary');
    inline-size: var(--size);
  }

  &__title {
    color: color('heading');
    font-weight: 700;
  }

  &__meta {
    list-style: none;
    padding-inline-start: 0;

    > * + * {
      margin-block-start: 0;
    }
  }

  &__action {
    display: flex;
    flex-wrap: wrap;
    gap: spacer('xs');
    margin-block-start: spacer('xs');
  }

  &__remove {
    display: none;
  }

  &__input {
    flex: 1;
  }
}
<div class="form-group">
    <label class="form-label" for="product-image">Product Image</label>
    <div class="file-group-container">
        <div class="file-group">
            <div class="file-group__preview">
                <svg class="file-group__icon" aria-hidden="true" focusable="false" fill="none" height="24" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" stroke="currentColor" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
                    <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
                    <circle cx="8.5" cy="8.5" r="1.5"></circle>
                    <polyline points="21 15 16 10 5 21"></polyline>
                </svg>
            </div>
            <div class="file-group__body">
                <p class="file-group__title">Upload a product image</p>
                <ul class="file-group__meta">
                    <li>Accepted formats: JPG, PNG</li>
                    <li>Maximum upload limit: 5 MB</li>
                </ul>
                <div class="file-group__action">
                    <button class="btn btn--delete btn--icon file-group__remove" aria-label="Remove file">
                        <svg class="btn__icon" aria-hidden="true" fill="none" focusable="false" height="24" stroke-linecap="round" stroke-linejoin="round" stroke-width="3.5" stroke="currentColor" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
                            <line x1="18" y1="6" x2="6" y2="18"></line>
                            <line x1="6" y1="6" x2="18" y2="18"></line>
                        </svg>
                    </button>
                    <input class="form-file file-group__input" id="product-image" type="file" accept="image/png, image/jpeg"/>
                </div>
            </div>
        </div>
    </div>
</div>
<div class="form-group">
    <label class="form-label" for="product-image-has-image">Product Image (has image)</label>
    <div class="file-group-container">
        <div class="file-group">
            <div class="file-group__preview" style="background-image: url('/img/post/gallery/kyle-johnson-Aq7id0ZjEW4-unsplash.jpg')">
                <svg class="file-group__icon" aria-hidden="true" focusable="false" fill="none" height="24" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" stroke="currentColor" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
                    <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
                    <circle cx="8.5" cy="8.5" r="1.5"></circle>
                    <polyline points="21 15 16 10 5 21"></polyline>
                </svg>
            </div>
            <div class="file-group__body">
                <p class="file-group__title">Upload a product image</p>
                <ul class="file-group__meta">
                    <li>Accepted formats: JPG, PNG</li>
                    <li>Maximum upload limit: 5 MB</li>
                </ul>
                <div class="file-group__action">
                    <button class="btn btn--delete file-group__remove" aria-label="Remove file">
                        <svg class="btn__icon" aria-hidden="true" fill="none" focusable="false" height="24" stroke-linecap="round" stroke-linejoin="round" stroke-width="3.5" stroke="currentColor" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
                            <line x1="18" y1="6" x2="6" y2="18"></line>
                            <line x1="6" y1="6" x2="18" y2="18"></line>
                        </svg>
                        Remove selected image
                    </button>
                    <input class="form-file file-group__input" id="product-image-has-image" type="file" accept="image/png, image/jpeg"/>
                </div>
            </div>
        </div>
    </div>
</div>

File Group

On this page

A file upload field with a preview section and place for metadata.

Technical Details

  • The component is an "extended" file upload field only. With your custom functionality, you can extend it to be an actual user-friendly component.
  • If the .file-group__preview has a background-image, it will automatically display the remove button and hide the blank slate icon. For this, it uses :has.
  • It also utilizes @container for breaking on mobile.

Documentation

Learn about Spruce CSS through our extensive documentation.

Components

Explore our extensive UI library built with Spruce CSS.

Blog

Read about front-end development and concepts of Spruce CSS.

Find us on GitHub

Did you find a bug? Have an idea or a question? Please open an issue to help us develop the project.