5

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?

Ben Carey
  • 16,540
  • 19
  • 87
  • 169

2 Answers2

2

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?

Temani Afif
  • 245,468
  • 26
  • 309
  • 415
  • Exactly! I tried to reduce the amount of `z-index` styles as much as I could but wasn't able to find a way like you have. Bear with me, I'll test this with my codebase now. +1 :-D – Ben Carey Jul 20 '19 at 22:43
0

Here we go. I think It could be solution for your scenario. I used opacity. Probably it can be polished but you get the idea.

Basically doing the "conflictive" items have opacity 0 when user clicks. Probably you can perform a better filter for items you want to "disappear" or not. For example having in mind position of item clicked and position of new focus.. Because you will notice the item now is not shown and you want it ocurs only when you go "from down to upper item".

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

  $('input').focus(function() {
    $(this).closest('.field-container').addClass('focused');
  });
  
  $('input').blur(function() {
    $(this).closest('.field-container').removeClass('focused');
  });
  $('input').on('click', function(){
  $('input').closest('.field-container').not('.focused').attr('style', 'opacity:0');
  });
  $('input').blur(function(){
    $('input').closest('.field-container').attr('style', 'opacity:1')
  })
  
});
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>
Sam
  • 1,459
  • 1
  • 18
  • 32
  • This does not fix the issue. If you are focused on the second input and you press `SHIFT` + `TAB`, you will see the issue. Not to mention, it makes the other inputs invisible when you are focused on one of them... – Ben Carey Jul 20 '19 at 21:42