2

I'm working on styling some user input and I came across a stumbling block.

I have nothing against the user's using autocomplete (I personally hate it when it's turned off on some sites) as I think it's really handy. I know how I can style it with -webkit-autofill. However, the autocomplete will always result in the fields getting a :valid state even if they shouldn't.

For example, I set a field to be minimum 5 chars long:

<input type="text" required class=namefileds id=first_name name=Name[] placeholder="Enter your first name" minlength="5">

I've set up the CSS styling for valid/invalid cases:

#checkout_form input:invalid {
    padding-right: calc(1.5em + .75rem);
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");
    background-repeat: no-repeat;
    background-position: center right calc(.375em + .1875rem);
    background-size: calc(.75em + .375rem) calc(.75em + .375rem);
}

    #checkout_form input:valid {
    padding-right: calc(1.5em + 0.75rem);
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23ffcc00' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");
    background-repeat: no-repeat;
    background-position: right calc(0.375em + 0.1875rem) center;
    background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
}

And this works fine. To make autofill style work nicely (on all webkit browsers) I added the following CSS rules:

@-webkit-keyframes autofillvalid {
     0%,100% {
         color: #ffcc00;
         background:none;
         padding-right: calc(1.5em + 0.75rem);
         background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23ffcc00' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");
         background-repeat: no-repeat;
         background-position: right calc(0.375em + 0.1875rem) center;
         background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
    }
}

 @-webkit-keyframes autofillinvalid {
    0%,100% {
        background:none;
        padding-right: calc(1.5em + .75rem);
        background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");
        background-repeat: no-repeat;
        background-position: center right calc(.375em + .1875rem);
        background-size: calc(.75em + .375rem) calc(.75em + .375rem);
    }
}

input:-webkit-autofill:valid {
     -webkit-animation-delay: 1s; /* Safari support - any positive time runs instantly */
     -webkit-animation-name: autofillvalid;
     -webkit-animation-fill-mode: both;
 }

 input:-webkit-autofill:invalid {
     -webkit-animation-delay: 1s; /* Safari support - any positive time runs instantly */
     -webkit-animation-name: autofillinvalid;
     -webkit-animation-fill-mode: both;
 }

My problem is that if the autofill has a name which is only four-character long, it should get the :invalid state, but autofill always generates the :valid state. Other states selectors looks to work fine, like for example: :focus

I had a look at this question, but I don't want to trigger additional java scripts for now, it's all about the visual at this stage, validation will come later after the user interacts with other things as well. And this is the problem, if the autofill gives the false feeling that the data is valid it can be really frustrating to be shown as invalid at a later stage...

Any idea how can this be styled from CSS only?

If I must use JS: The other problem is that style does not get updated on the filed till the value is not manually changed, triggering a jQuery .change() event on fields is not helping

As writing the question I decided to do a bit more digging, and to make matters worse, autofill makes the element document.getElementById("first_name").checkValidity() return true, when it should not.

So while I started this with a CSS question, now I'm more inclined to ask can this be behaviour considered a bug?

Edit 1: Since the checkValidity() returns true, I tried to submit the form (which normally should not be submitted because the validation conditions are not met) and it did submit without any hiccups. Of course the golden rule apply never trust user data and server side will reject it, but I think this should not happen a simple autofill should not automatically mean data is valid.

Richard
  • 7,037
  • 2
  • 23
  • 76
Emil Borconi
  • 3,326
  • 2
  • 24
  • 40

2 Answers2

4

I hope its help:

use the "pattern" attribute instead of "minlength".

#checkout_form input:invalid{
    padding-right: calc(1.5em + .75rem);
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");
    background-repeat: no-repeat;
    background-position: center right calc(.375em + .1875rem);
    background-size: calc(.75em + .375rem) calc(.75em + .375rem);
    }
    #checkout_form input:valid{
    padding-right: calc(1.5em + 0.75rem);
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23ffcc00' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");
    background-repeat: no-repeat;
    background-position: right calc(0.375em + 0.1875rem) center;
    background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
    }
    
    @-webkit-keyframes autofillvalid {
    0%,100% {
        color: #ffcc00;
        background:none;
         padding-right: calc(1.5em + 0.75rem);
        background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23ffcc00' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");
        background-repeat: no-repeat;
        background-position: right calc(0.375em + 0.1875rem) center;
        background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
    }
}
    @-webkit-keyframes autofillinvalid {
    0%,100% {
        background:none;
        padding-right: calc(1.5em + .75rem);
        background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");
        background-repeat: no-repeat;
        background-position: center right calc(.375em + .1875rem);
        background-size: calc(.75em + .375rem) calc(.75em + .375rem);
    }
}

    input:-webkit-autofill:valid {
        -webkit-animation-delay: 1s; /* Safari support - any positive time runs instantly */
        -webkit-animation-name: autofillvalid;
        -webkit-animation-fill-mode: both;
    }
    input:-webkit-autofill:invalid {
        -webkit-animation-delay: 1s; /* Safari support - any positive time runs instantly */
        -webkit-animation-name: autofillinvalid;
        -webkit-animation-fill-mode: both;
    }
<form id="checkout_form" >
  <input type="text" required class=namefileds id=first_name name=Name[] placeholder="Enter your first name" pattern=".{5,}">
  <button type="submit">submit</button>
</form>
Fahim Khan
  • 395
  • 2
  • 7
  • Thanks. You're answer was correct, however the other answer was way more complete, and went on explaining on why to use pattern instead of minLenght and pointed out what was actually going wrong with the validation, so the bounty was awarded to @Richard – Emil Borconi Mar 05 '20 at 08:54
3

This is a really interesting question. However, I am curious about something. Chrome will only save autocomplete values on form submission (as mentioned here). Logically, if the input has a minlength of 5 and you input something with a length of 4, you won't be able to submit the form in the first place. Therefore, the invalid autocomplete value shouldn't have been saved. How did you first encounter this situation? I was only able to reproduce it by not setting minlength, submit, then setting minlength and tried the autofill.

To answer your question, this behaviour is not a bug. minlength is not working because it's not designed such that it can validate values inserted by autofill. Read more below.

Why doesn't minlength work?

Why does minlength not invalidate values given by autocomplete when the values' length are less than the specified minlength? Mentioned in the HTML Standard by WHATWG, minlength has this constraint validation:

If an element has a minimum allowed value length, its dirty value flag is true, its value was last changed by a user edit (as opposed to a change made by a script), its value is not the empty string, and the JavaScript string length of the element's API value is less than the element's minimum allowed value length, then the element is suffering from being too short.

It becomes obvious that the value will only be validated by minlength when its value was changed by user edit and not by a script. Reading further, we can infer that autofill's values are inserted by a script (read here). We can test minlength not validating input's values when they are altered by a script by doing the following (please insert the word "Testing" to the input):

const input = document.querySelector('input')
input.addEventListener('keyup', e => {
  if (input.value === "Testing") input.value = "Test"
})
input:invalid {
  outline: 1px solid red;
}
<input type="text" minlength="5">

You can see that it does not bother to validate the input when its value is changed to "Test" by the script.

Using pattern

pattern, on the other hand, does not have the constraint validation saying that values must be changed by user edit. You can read about its constraint validation here. Therefore, you can still change the value of input using a script and it will still work. Try inputting "Testing" to the input again:

const input = document.querySelector('input')
input.addEventListener('keyup', e => {
  if (input.value === "Testing") input.value = "Test"
})
input:invalid {
  outline: 1px solid red;
}
<input type="text" pattern="[0-9a-zA-Z]{5,}">

When it changes to just "Test", it is invalidated by pattern and causes the CSS to trigger. This means that you can use pattern to validate the values given by autofill. (Still curious how this erroneous autofill value is saved in the first place.)

Here's a solution using pattern to validate your input given by autofill with some adjustments:

* {
  box-sizing: border-box;
  margin: 0;
  font-family: Helvetica;
}

body {
  padding: 20px;
}

#checkout_form * {
  display: block;
  margin: 1em;
}

#first_name {
  padding: 10px;
  border-radius: 5px;
  width: 400px;
  border: 1px solid #00000044;
  outline: none;
}

#first_name::placeholder {
  color: #00000040;
}

#submit_form {
  padding: 10px;
  border-radius: 5px;
  border: none;
  background: #7F00FF;
  color: #ffffffee;
  width: 200px;
}

#checkout_form input:invalid {
  padding-right: calc(1.5em + .75rem);
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");
  background-repeat: no-repeat;
  background-position: center right calc(.375em + .1875rem);
  background-size: calc(.75em + .375rem) calc(.75em + .375rem);
  
  border: 1px solid #CC0000ee;
}

#checkout_form input:valid {
  padding-right: calc(1.5em + 0.75rem);
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23ffcc00' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");
  background-repeat: no-repeat;
  background-position: right calc(0.375em + 0.1875rem) center;
  background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
  
  border: 1px solid #00cc00ee;
}

@-webkit-keyframes autofillvalid {
  0%,
  100% {
    color: #ffcc00;
    background: none;
    padding-right: calc(1.5em + 0.75rem);
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23ffcc00' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");
    background-repeat: no-repeat;
    background-position: right calc(0.375em + 0.1875rem) center;
    background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
  }
}

@-webkit-keyframes autofillinvalid {
  0%,
  100% {
    background: none;
    padding-right: calc(1.5em + .75rem);
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");
    background-repeat: no-repeat;
    background-position: center right calc(.375em + .1875rem);
    background-size: calc(.75em + .375rem) calc(.75em + .375rem);
  }
}

input:-webkit-autofill:valid {
  -webkit-animation-name: autofillvalid;
  -webkit-animation-fill-mode: both;
}

input:-webkit-autofill:invalid {
  -webkit-animation-name: autofillinvalid;
  -webkit-animation-fill-mode: both;
}
<form id="checkout_form">
  <input type="text" required id="first_name" name="fname" placeholder="Enter your first name" pattern="[0-9a-zA-Z]{5,}" autocomplete="given-name" title="First Name should be at least 5 characters and must consist only of alphabets and/or numbers">
  <button id="submit_form" type="submit">Submit</button>
</form>

Furthermore, I highly recommend reading more about autofill and how to utilize it better here.

Richard
  • 7,037
  • 2
  • 23
  • 76
  • You're answer makes a lot of sense I will check it out in a couple of days as I'm on the road. I put that minlength value there just to check if my styling works correctly for valid/invalid fields (not that I really needed it there) but looks like I've manage to pick a very wrong constrain to do tests with... – Emil Borconi Mar 03 '20 at 20:36
  • @EmilBorconi Great! Tell me how the answer works out for you later. – Richard Mar 04 '20 at 02:21