javascripthtmlcss

Ways to retain a focus ring on a transparent element


I'm in the process of building an input in a form that contains a profile picture that, when clicked, will allow a user to select a new image. It looks like the common practice is to use an input with the type of "file" and then hide it through various means. Setting opacity:0 seems to be the only legitimate option to me, as this will keep the element in the DOM and it will still detect clicks or focus.

However, an input with no opacity will also not have a visible focus ring, even though the element is technically in focus. What are the options I have at my disposal in order to have a focus ring show when focused? Thank you in advance.

.profile-pic {
  width: 156px;
  height: 156px;
  border-radius: 50%;
  position: relative;
  /* overflow: hidden; */
}

.profile-pic input[type='file'] {
  opacity: 0;
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  padding: 0;
  border: none;
  border-radius: 50%;
  cursor: pointer;
  z-index: 10;
}

.profile-pic__img {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  overflow: hidden;
  z-index: 1;
}

.profile-pic__img img {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: brightness(0.65);
  transition: filter 0.2s ease-in-out;
  cursor: pointer;
}

.profile-pic__img img:hover {
  filter: brightness(0.5);
}

.profile-pic__text {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 90%;
  display: flex;
  flex-direction: column;
  align-items: center;
  z-index: 5;
}

.profile-pic__text .fa-camera {
  color: #fff;
}

.profile-pic span {
  color: #fff;
  text-align: center;
  font-size: 16px;
  font-style: normal;
  font-weight: 400;
  line-height: normal;
  width: 90%;
}
<div class="profile-pic">
  <input type="file" />
  <div class="profile-pic__img">
    <img src="[TEST_URL]" alt="" />
  </div>
  <div class="profile-pic__text">
    <i class="fas fa-camera"></i>
    <span>Click to change photo</span>
  </div>
</div>


Solution

  • Apply the :focus-within pseudo-class to the .profile-pic and give it distinctive styling when the transparent input has the focus.

    .profile-pic {
      width: 156px;
      height: 156px;
      border-radius: 50%;
      position: relative;
      /* overflow: hidden; */
    }
    
    .profile-pic:focus-within {
        outline: dotted #777 2px;
    }
    
    .profile-pic input[type='file'] {
      opacity: 0;
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      padding: 0;
      border: none;
      border-radius: 50%;
      cursor: pointer;
      z-index: 10;
    }
    
    .profile-pic__img {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      border-radius: 50%;
      overflow: hidden;
      z-index: 1;
    }
    
    .profile-pic__img img {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      object-fit: cover;
      filter: brightness(0.65);
      transition: filter 0.2s ease-in-out;
      cursor: pointer;
    }
    
    .profile-pic__img img:hover {
      filter: brightness(0.5);
    }
    
    .profile-pic__text {
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      width: 90%;
      display: flex;
      flex-direction: column;
      align-items: center;
      z-index: 5;
    }
    
    .profile-pic__text .fa-camera {
      color: #fff;
    }
    
    .profile-pic span {
      color: #fff;
      text-align: center;
      font-size: 16px;
      font-style: normal;
      font-weight: 400;
      line-height: normal;
      width: 90%;
    }
    <div class="profile-pic">
      <input type="file" />
      <div class="profile-pic__img">
        <img src="[TEST_URL]" alt="" />
      </div>
      <div class="profile-pic__text">
        <i class="fas fa-camera"></i>
        <span>Click to change photo</span>
      </div>
    </div>