Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

z-index issue with custom form field (select)

Background

Consider the following custom input (ignore the JS):

$(document).ready(() => {

  $('input').focus(function() {
    $(this).closest('.field-container').addClass('focused');
  });
  
  $('input').blur(function() {
    $(this).closest('.field-container').removeClass('focused');
  });
  
});
html, body {
  background: #eee;
}

.field-container {
  display: flex;
  padding: 12px 10px 0;
  position: relative;
  transition: z-index 0s cubic-bezier(0.4, 0, 0.2, 1) 0.3s;
  width: 50%;
  z-index: 1;
}

.field-container.focused {
  transition-delay: 0s;
  z-index: 11;
}

.field-container.focused:before {
  opacity: 1;
  transform: scaleX(1);
  transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  transition-property: border, opacity, transform;
}

.field-container.focused label {
  font-size: 15px;
  opacity: 1;
  pointer-events: auto;
  top: 0;
}

.field-container.focused .select-form-control .options-form-control {
  opacity: 1;
  visibility: visible;
}

.field-container:before,
.field-container:after {
  bottom: 0;
  content: "";
  left: 0;
  position: absolute;
  right: 0;
  transition: border 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), transform 0s cubic-bezier(0.4, 0, 0.2, 1) 0.3s;
  will-change: border, opacity, transform;
}

.field-container:before {
  background: #000;
  height: 2px;
  opacity: 0;
  transform: scaleX(0.12);
  z-index: 11;
}

.field-container:after {
  background: #ccc;
  height: 1px;
  z-index: 10;
}

.field-container label {
  color: #ccc;
  font-size: 21px;
  font-weight: 500;
  pointer-events: none;
  position: absolute;
  top: 25px;
  transition: 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
  transition-duration: 0.3s;
  z-index: 10;
}

.field-container .select-form-control {
  display: flex;
  position: relative;
  width: 100%;
  z-index: 9;
}

.field-container input {
  background: none;
  border: none;
  color: #000;
  cursor: text;
  display: block;
  flex: 1;
  font-size: 21px;
  font-weight: 500;
  height: 56px;
  line-height: 56px;
  margin: 0;
  min-width: 100px;
  outline: none;
  padding: 0;
  text-rendering: auto;
  transition: 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
  transition-property: font-size, padding-top, color;
  word-spacing: normal;
  -webkit-appearance: textfield;
  -webkit-rtl-ordering: logical;
  -webkit-writing-mode: horizontal-tb !important;
}

.field-container .select-form-control .options-form-control {
  background: rgba(255, 255, 255, 0.95);
  box-shadow: 0 23px 71px 0 rgba(204, 204, 204, 0.09);
  left: -20px;
  opacity: 0;
  padding-top: 90px;
  position: absolute;
  right: -20px;
  top: -22px;
  transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), visibility 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  visibility: hidden;
  z-index: -1;
}

.field-container .select-form-control .options-form-control ul {
  list-style-type: none;
  max-height: 200px;
  overflow: auto;
  padding: 0 0 10px;
  margin: 0;
}

.field-container .select-form-control .options-form-control ul li {
  color: #000;
  cursor: pointer;
  display: block;
  font-size: 21px;
  font-weight: 500;
  line-height: 2.12;
  padding: 0 20px;
  z-index: -1;
  margin: 0;
}

.field-container .select-form-control .options-form-control ul li:hover {
  background: #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="field-container foo">
  <label>Foo</label>
  <div class="select-form-control">
    <input name="foo" autocomplete="new-password" readonly="readonly">
    <div class="options-form-control">
      <ul>
        <li class="active">Foo</li>
        <li class="">Bar</li>
        <li class="">Foobar</li>
      </ul>
    </div>
  </div>
</div>

<div class="field-container bar">
  <label>Bar</label>
  <div class="select-form-control">
    <input name="bar" autocomplete="new-password" readonly="readonly">
    <div class="options-form-control">
      <ul>
        <li class="active">Foo</li>
        <li class="">Bar</li>
        <li class="">Foobar</li>
      </ul>
    </div>
  </div>
</div>

I apologise for the quantity of CSS, I tried to strip out as much as possible but needed the functionality to be there in order to demonstrate the issue...

The Problem

Forgive me for being somewhat of a perfectionist, but I have noticed a very slight issue that is rather bugging me and I cannot seem to come up with a fix for it.

When the field is focused, and the .options-form-control element is shown, it needs to be above all other content (aside from the input and label siblings). I have acheived this by adjusting the z-index of each of the elements within the .field-container.

The problem is, when a user focuses the previous input from having focus on the next input (from .bar to .foo), as the dropdown transitions in, the input and label within the .bar element, show above the dropdown that is being transitioned in (for .3s).

I know why it is doing this, but I cannot think of a way to solve it, especially without restructuring the entire markup which isn't really an option because it is used within other components in my app.

Does anyone have any suggestions as to how I can get around this?

like image 579
Ben Carey Avatar asked Oct 28 '22 11:10

Ben Carey


1 Answers

The main issue is due to the use of a lot of z-index on nested elements which will give you headaches dealing with stacking context. In order to avoid this bad effect, I removed a lot of z-index properties and kept only the needed ones.

You will only find 4 z-index

$(document).ready(() => {

  $('input').focus(function() {
    $(this).closest('.field-container').addClass('focused');
  });
  
  $('input').blur(function() {
    $(this).closest('.field-container').removeClass('focused');
  });
  
});
html, body {
  background: #eee;
}

.field-container {
  display: flex;
  padding: 12px 10px 0;
  position: relative;
  width: 50%;
}


.field-container.focused:before {
  opacity: 1;
  transform: scaleX(1);
  transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  transition-property: border, opacity, transform;
  z-index: 11; /* Here */
}

.field-container.focused label {
  font-size: 15px;
  opacity: 1;
  pointer-events: auto;
  top: 0;
  z-index: 10; /* Here */
}

.field-container.focused .select-form-control .options-form-control {
  opacity: 1;
  visibility: visible;
}

.field-container:before,
.field-container:after {
  bottom: 0;
  content: "";
  left: 0;
  position: absolute;
  right: 0;
  transition: border 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), transform 0s cubic-bezier(0.4, 0, 0.2, 1) 0.3s;
  will-change: border, opacity, transform;
}

.field-container:before {
  background: #000;
  height: 2px;
  opacity: 0;
  transform: scaleX(0.12);
}

.field-container:after {
  background: #ccc;
  height: 1px;
}

.field-container label {
  color: #ccc;
  font-size: 21px;
  font-weight: 500;
  pointer-events: none;
  position: absolute;
  top: 25px;
  transition: 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
  transition-duration: 0.3s;
}

.field-container .select-form-control {
  display: flex;
  position: relative;
  width: 100%;
}

.field-container input {
  background: none;
  border: none;
  color: #000;
  cursor: text;
  display: block;
  flex: 1;
  font-size: 21px;
  font-weight: 500;
  height: 56px;
  line-height: 56px;
  margin: 0;
  min-width: 100px;
  outline: none;
  padding: 0;
  text-rendering: auto;
  transition: 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
  transition-property: font-size, padding-top, color;
  word-spacing: normal;
  -webkit-appearance: textfield;
  -webkit-rtl-ordering: logical;
  -webkit-writing-mode: horizontal-tb !important;
}

.field-container .select-form-control .options-form-control {
  background: rgba(255, 255, 255, 0.95);
  box-shadow: 0 23px 71px 0 rgba(204, 204, 204, 0.09);
  left: -20px;
  opacity: 0;
  padding-top: 90px;
  position: absolute;
  right: -20px;
  top: -22px;
  transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), visibility 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  visibility: hidden;
  z-index: 2; /* Here */
}

.field-container .select-form-control .options-form-control ul {
  list-style-type: none;
  max-height: 200px;
  overflow: auto;
  padding: 0 0 10px;
  margin: 0;
}

.field-container .select-form-control .options-form-control ul li {
  color: #000;
  cursor: pointer;
  display: block;
  font-size: 21px;
  font-weight: 500;
  line-height: 2.12;
  padding: 0 20px;
  z-index: -1; /* Here */
  margin: 0;
}

.field-container .select-form-control .options-form-control ul li:hover {
  background: #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="field-container foo">
  <label>Foo</label>
  <div class="select-form-control">
    <input name="foo" autocomplete="new-password" readonly="readonly">
    <div class="options-form-control">
      <ul>
        <li class="active">Foo</li>
        <li class="">Bar</li>
        <li class="">Foobar</li>
      </ul>
    </div>
  </div>
</div>

<div class="field-container bar">
  <label>Bar</label>
  <div class="select-form-control">
    <input name="bar" autocomplete="new-password" readonly="readonly">
    <div class="options-form-control">
      <ul>
        <li class="active">Foo</li>
        <li class="">Bar</li>
        <li class="">Foobar</li>
      </ul>
    </div>
  </div>
</div>

Related question for more details about z-index and stacking context:

Why can't an element with a z-index value cover its child?

like image 50
Temani Afif Avatar answered Nov 11 '22 06:11

Temani Afif