Skip to content

Styling an HTML file input for images

In this post, we’ll see how to style an input element used to add images:

Into one that adds images and generates a preview with a “+” button:

Add image


(Clicking on the “submit” button will print a list of the added image files)

We’ll obtain this result by using a second input as a helper, storing the added image files in an array, and setting the input files before submitting.

Markup

Our markup will consist of a form with two input elements.

<form class="custom__form">
  <p>Add image</p>
  <div class="custom__image-container">
    <label id="add-img-label" for="add-single-img">+</label>
    <input type="file" id="add-single-img" accept="image/jpeg" />
  </div>
  <input
    type="file"
    id="image-input"
    name="photos"
    accept="image/jpeg"
    multiple
  />
  <br />
  <div class="form__controls"><button type="submit">Submit</button></div>
</form>

The first input element add-single-img will be used to add the image files one by one. However, the input image-input is the one we really care about when submitting the form.

One important aspect is that the + label element is associated with the helper input via the for attribute. This way, when we click on it, the input is triggered.

Styling

Both input elements will be hidden because it’s difficult to modify and style them directly.

.custom__form input {
  opacity: 0;
  height: 0;
}

The “+” button used to select images is placed inside a container. As we select images, a preview of them will be added to this same container.

.custom__image-container {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

Since the helper input element is hidden, we interact with it via its label (important to set the for attribute accordingly). We style it to look like a square button.

.custom__image-container label {
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 150%;
  cursor: pointer;
  width: 100px;
  height: 100px;
  border: solid 1px black;
  border-radius: 5px;
  object-fit: cover;
}

Similar rules are applied to img elements that will be added to the container as we select new image files.

.custom__image-container img {
  width: 100px;
  height: 100px;
  border: solid 1px black;
  border-radius: 5px;
  object-fit: cover;
}

Scripts

We start by defining the following global variables.

const imgInputHelper = document.getElementById("add-single-img");
const imgInputHelperLabel = document.getElementById("add-img-label");
const imgContainer = document.querySelector(".custom__image-container");
const imgFiles = [];

The first three are there only for convenience. The global imgFiles will store the image files that the user selects.

Adding image previews

Every time we select an image via the + button, a preview of it will be generated. We’ll trigger this effect via a “change” event listener on the helper input element.

const addImgHandler = () => {
  const file = imgInputHelper.files[0];
  if (!file) return;

  // Generate img preview
  const reader = new FileReader();
  reader.readAsDataURL(file);
  reader.onload = () => {
    const newImg = document.createElement("img");
    newImg.src = reader.result;
    imgContainer.insertBefore(newImg, imgInputHelperLabel);
  };

  // Store img file
  imgFiles.push(file);

  // Reset image input
  imgInputHelper.value = "";
  return;
};

imgInputHelper.addEventListener("change", addImgHandler);

To generate the image preview, we used a FileReader. A more direct approach would use the URL.createObjectURL method, as can be seen in this example.

Creating a list of files

We stored the added images in the imgFiles array. Input elements store files in FileList objects. There is no “FileList” constructor. We will create it indirectly via a DataTransfer object.

const getImgFileList = (imgFiles) => {
  const imgFilesHelper = new DataTransfer();
  imgFiles.forEach((imgFile) => imgFilesHelper.items.add(imgFile));
  return imgFilesHelper.files;
};

The function above takes the array of Files imgFiles that we used previously to store the image files. It returns a FileList generated by using the DataTransfer object.

Credit for this approach goes to this StackOverflow answer.

Submitting the list of files

Before the actual form submission, we will take the files stored in imgFiles, create a “FileList” and assign it to the input element (not the helper).

const customFormSubmitHandler = (ev) => {
  ev.preventDefault();
  const firstImgInput = document.getElementById("image-input");
  firstImgInput.files = getImgFileList(imgFiles);
  // submit form to server, etc
};

document
  .querySelector(".custom__form")
  .addEventListener("submit", customFormSubmitHandler);

As a result, we’ll have our original input element image-input with a FileList as if we had selected them directly using the native element.


Access the source code here.

Published inProgramming
Subscribe
Notify of
guest
2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Liam Crespo Bergström
Liam Crespo Bergström
1 year ago

THANK YOUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU

Liam Crespo Bergström
Liam Crespo Bergström
1 year ago

But wait how do u make it so that it doesn’t restart/repeat a new input field, lets say you only need 5?