Detecting Browser Autofill

Viewed 227012

How do you tell if a browser has auto filled a text-box? Especially with username & password boxes that autofill around page load.

My first question is when does this occur in the page load sequence? Is it before or after document.ready?

Secondly how can I use logic to find out if this occurred? Its not that i want to stop this from occurring, just hook into the event. Preferably something like this:

if (autoFilled == true) {

} else {

}

If possible I would love to see a jsfiddle showing your answer.

Possible duplicates

DOM event for browser password autofill?

Browser Autofill and Javascript triggered events

--Both these questions don't really explain what events are triggered, they just continuously recheck the text-box (not good for performance!).

39 Answers

I was reading a lot about this issue and wanted to provide a very quick workaround that helped me.

let style = window.getComputedStyle(document.getElementById('email'))
  if (style && style.backgroundColor !== inputBackgroundNormalState) {
    this.inputAutofilledByBrowser = true
  }

where inputBackgroundNormalState for my template is 'rgb(255, 255, 255)'.

So basically when browsers apply autocomplete they tend to indicate that the input is autofilled by applying a different (annoying) yellow color on the input.

Edit : this works for every browser

I also faced the same problem where label did not detect autofill and animation for moving label on filling text was overlapping and this solution worked for me.

input:-webkit-autofill ~ label {
    top:-20px;
} 

I was looking for a similar thing. Chrome only... In my case the wrapper div needed to know if the input field was autofilled. So I could give it extra css just like Chrome does on the input field when it autofills it. By looking at all the answers above my combined solution was the following:

/* 
 * make a function to use it in multiple places
 */
var checkAutoFill = function(){
    $('input:-webkit-autofill').each(function(){
        $(this).closest('.input-wrapper').addClass('autofilled');
    });
}

/* 
 * Put it on the 'input' event 
 * (happens on every change in an input field)
 */
$('html').on('input', function() {
    $('.input-wrapper').removeClass('autofilled');
    checkAutoFill();
});

/*
 * trigger it also inside a timeOut event 
 * (happens after chrome auto-filled fields on page-load)
 */
setTimeout(function(){ 
    checkAutoFill();
}, 0);

The html for this to work would be

<div class="input-wrapper">
    <input type="text" name="firstname">
</div>

Here is CSS solution taken from Klarna UI team. See their nice implementation here resource

Works fine for me.

input:-webkit-autofill {
  animation-name: onAutoFillStart;
  transition: background-color 50000s ease-in-out 0s;
}
input:not(:-webkit-autofill) {
  animation-name: onAutoFillCancel;
}

There is a trick to understand browser fills the input or not (boolean):

const inputEl = inputRef.current; // select the el with any way, here is ReactJs ref
let hasValue;
try {
  hasValue = inputRef.current.matches(':autofill');
} catch (err) {
  try {
    hasValue = inputRef.current.matches(':-webkit-autofill');
  } catch (er) {
    hasValue = false;
  }
}

// hasValue (boolean) is ready

After the last curly brace the hasValue is ready to use. you are able to detect the browser autofill happened or not.

In Chrome and Edge (2020) checking for :-webkit-autofill will tell you that the inputs have been filled. However, until the user interacts with the page in some way, your JavaScript cannot get the values in the inputs.

Using $('x').focus() and $('x').blur() or triggering a mouse event in code don't help.

See https://stackoverflow.com/a/35783761/32429

I spent few hours resolving the problem of detecting autofill inputs on first page load (without any user action taken) and found ideal solution that works on Chrome, Opera, Edge and on FF too!!

On Chrome, Opera, Edge problem was solved quite EZ

by searching elements with pseudoclass input:-webkit-autofill and doing desired actions (in my case i was changing input wrapper class to change label positions with float label pattern).

The problem was with Firefox

becouse FF does not have such pseudoclass or similar class (as many suggest ":-moz-autofill") that is visible by simply searching DOM. You also can't find the yellow background of input. The only cause is that browser adds this yellow color by changing filter property:

input:-moz-autofill, input:-moz-autofill-preview { filter: grayscale(21%) brightness(88%) contrast(161%) invert(10%) sepia(40%) saturate(206%); }

So in case of Firefox You must first search all inputs and get its computed style and then compare to this filter style hardcoded in browser settings. I really dunno why they didnt use simply background color but that strange filter!? They making lifes harder ;)

Here is my code working like a charm at my website (https://my.oodo.pl/en/modules/register/login.php):

<script type="text/javascript">
/* 
 * this is my main function
 */
var checkAutoFill = function(){
    /*first we detect if we have FF or other browsers*/
    var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
    if (!isFirefox) {
        $('input:-webkit-autofill').each(function(){
        /*here i have code that adds "focused" class to my input wrapper and changes 
        info instatus div. U can do what u want*/
        $(this).closest('.field-wrapper').addClass('focused');
        document.getElementById("status").innerHTML = "Your browser autofilled form";
        });
    }
    if (isFirefox) {
        $('input').each(function(){
        var bckgrnd = window.getComputedStyle(document.getElementById(this.id), null).getPropertyValue("background-image");
        if (bckgrnd === 'linear-gradient(rgba(255, 249, 145, 0.5), rgba(255, 249, 145, 0.5))') {
        /*if our input has that filter property customized by browserr with yellow background i do as above (change input wrapper class and change status info. U can add your code here)*/
        $(this).closest('.field-wrapper').addClass('focused');
        document.getElementById("status").innerHTML = "Your Browser autofilled form";
        }
        })
    }
}
/*im runing that function at load time and two times more at 0.5s and 1s delay because not all browsers apply that style imediately (Opera does after ~300ms and so Edge, Chrome is fastest and do it at first function run)*/
checkAutoFill();
setTimeout(function(){ 
checkAutoFill();
}, 500);
setTimeout(function(){ 
checkAutoFill();
}, 1000);
})
</script>

I edited above code manually here to throw out some trash not important for You. If its not working for u, than paste into Your IDE and double check the syntax ;) Of course add some debuging alerts or console logs and customize.

in 2020, this is what worked for me in chrome:

// wait 0.1 sec to execute action after detecting autofill
// check if input username is autofilled by browser
// enable "login" button for click to submit form
 $(window).on("load", function(){
       setTimeout(function(){

           if ($("#UserName").is("input:-webkit-autofill")) 
           $("#loginbtn").prop('disabled', false); 

      }, 100);
 });

After research the issue is that webkit browsers does not fire change event on autocomplete. My solution was to get the autofill class that webkit adds and trigger change event by myself.

setTimeout(function() {
 if($('input:-webkit-autofill').length > 0) {
   //do some stuff
 }
},300)

Here is a link for the issue in chromium. https://bugs.chromium.org/p/chromium/issues/detail?id=636425

I had a hard time detecting auto-fill in Firefox. This is the only solution that worked for me:

Demo

HTML:

<div class="inputFields">
   <div class="f_o">
      <div class="field_set">
        <label class="phold">User</label>
        <input type="tel" class="form_field " autocomplete="off" value="" maxlength="50">
      </div>
   </div>
   <div class="f_o">
      <div class="field_set">
         <label class="phold">Password</label>
         <input type="password" class="form_field " autocomplete="off" value="" maxlength="50">
      </div>
   </div>
</div>

CSS:

/* Detect autofill for Chrome */
.inputFields input:-webkit-autofill {
    animation-name: onAutoFillStart;
    transition: background-color 50000s ease-in-out 0s;
}
.inputFields input:not(:-webkit-autofill) {
    animation-name: onAutoFillCancel;
}

@keyframes onAutoFillStart {
}

@keyframes onAutoFillCancel {
}
.inputFields {
  max-width: 414px;
}

.field_set .phold{
  display: inline-block;
  position: absolute;
  font-size: 14px;
  color: #848484;
  -webkit-transform: translate3d(0,8px,0);
  -ms-transform: translate3d(0,8px,0);
  transform: translate3d(0,8px,0);
  -webkit-transition: all 200ms ease-out;
  transition: all 200ms ease-out;
  background-color: transparent;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
  margin-left: 8px;
  z-index: 1;
  left: 0;
  pointer-events: none;
}

.field_set .phold_active {
   font-size: 12px;
   -webkit-transform: translate3d(0,-8px,0);
  -ms-transform: translate3d(0,-8px,0);
  transform: translate3d(0,-8px,0);
  background-color: #FFF;
  padding-left: 3px;
  padding-right: 3px;
}

.field_set input[type='text'], .field_set select, .field_set input[type='tel'], .field_set input[type='password'] {
    height: 36px;
}

.field_set input[type='text'], .field_set input[type='tel'], .field_set input[type='password'], .field_set select, .field_set textarea {
    box-sizing: border-box;
    width: 100%;
    padding: 5px;
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    border: 1px solid #ababab;
    border-radius: 0;
}

.field_set {
    margin-bottom: 10px;
    position: relative;
}

.inputFields .f_o {
    width: 100%;
    line-height: 1.42857143;
    float: none;
}

JavaScript:

    // detect auto-fill when page is loading
  $(window).on('load', function() {
    // for sign in forms when the user name and password are filled by browser
    getAutofill('.inputFields');
  });

  function getAutofill(parentClass) {
    if ($(parentClass + ' .form_field').length > 0) {    
      var formInput = $(parentClass + ' .form_field');
      formInput.each(function(){   
        // for Chrome:  $(this).css('animation-name') == 'onAutoFillStart'
        // for Firefox: $(this).val() != ''
        if ($(this).css('animation-name') == 'onAutoFillStart' || $(this).val() != '') {
          $(this).siblings('.phold').addClass('phold_active');
        } else {
          $(this).siblings('.phold').removeClass('phold_active');
        }
      });
    }
  } 

  $(document).ready(function(){

    $(document).on('click','.phold',function(){
      $(this).siblings('input, textarea').focus();
    });
    $(document).on('focus','.form_field', function(){
      $(this).siblings('.phold').addClass('phold_active');
    });

    // blur for Chrome and change for Firefox
    $(document).on('blur change','.form_field', function(){
      var $this = $(this);
      if ($this.val().length == 0) {        
        $(this).siblings('.phold').removeClass('phold_active');
      } else {
        $(this).siblings('.phold').addClass('phold_active');
      }
    });

    // case when form is reloaded due to errors
    if ($('.form_field').length > 0) {
      var formInput = $('.form_field');
      formInput.each(function(){
        if ($(this).val() != '') {
          $(this).siblings('.phold').addClass('phold_active');
        } else {
          $(this).siblings('.phold').removeClass('phold_active');
        }
      });
    }

  }); 

For Chrome I use: if ($(this).css('animation-name') == 'onAutoFillStart')

For Firefox: if ($(this).val() != '')

For anyone looking for a 2020 pure JS solution to detect autofill, here ya go.

Please forgive tab errors, can't get this to sit nicely on SO

    //Chose the element you want to select - in this case input
    var autofill = document.getElementsByTagName('input');
    for (var i = 0; i < autofill.length; i++) {
      //Wrap this in a try/catch because non webkit browsers will log errors on this pseudo element
      try{
        if (autofill[i].matches(':-webkit-autofill')) {
            //Do whatever you like with each autofilled element
        }
      }
      catch(error){
        return(false);
      }
     }
$('selector').on('keyup', aFunction);
// If tab is active, auto focus for trigger event keyup, blur, change...
// for inputs has been autofill
$(window).on('load', () => {
  if (!document.hidden) {
    window.focus();
  }
})

This works for me. Tested on Chrome.

you can try this to detect and clear out all autofill

 var autofillclear = false;
  setInterval(function() {
    if ($("input:-webkit-autofill") && autofillclear == false) {
      $("input:-webkit-autofill").each(function() {
        if ($(this).val() != '') {
          $(this).val('');
          autofillclear = true;
        }
      });
    }
   }, 500);

I had this issue when using Instagram autofill for email and phone inputs, tried different solutions but nothing worked, At the end all I had to do to disable the autofill is to have different name attributes for phone and email that did the trick.

I found a working solution for angularjs.

The trick is to disable the required-property from the input field when the directive detects that the field was populated by the browser via auto fill.

As the input field is no longer required, the login submit buttons is enabled.

This works even if the user didn't click into the body of the window (see Chrome Autofill/Autocomplete no value for password).

Directive:

angular.module('formtools').directive('autofill', [
        '$interval', function ($interval)
        {
            return {
                scope: false,
                require: 'autofill',
                controller: function AutoFillController(){
                    this.applied = false;
                },
                controllerAs: 'autoFill',
                link: function (scope, elem, attrs, autofill)
                {
                    var refresh = $interval(function() {
                        // attention: this needs jquery, jqlite from angular doesn't provide this method
                        if(elem.is(':-webkit-autofill'))
                        {
                            autofill.applied = true;
                            $interval.cancel(refresh);
                        }
                    }, 100, 100);
                }
            }
        }]);

HTML:

<form name="loginform">

  <input 
     type="text" 
     name="username" 
     autofill 
     ng-required="!autoFill.applied">
  
  <input 
     type="password" 
     name="password" 
     autofill 
     ng-required="!autoFill.applied">
     
  <button ng-disabled="loginform.$invalid">Login</button>   
</form>

Well, If we are talking about a controlled inputs, this could be one of possible solutions:

    useEffect(() => {
      const inputEl = inputRef.current
      inputEl.addEventListener('focusout', () => props.onChange(inputRef.value))
    }, [])

Note that this may cause unnecessary rerender. The advantages of this solution are its simplicity and support by all browsers. It seems that the use of this approach in small forms is quite acceptable.

Additionally to the other answers, I like to add this helper function, which prevents script errors and which you can use for example on DOMContentLoaded or onload (both together with setTimeout >=500, because there is some delay until the browser does autofill), at onchange on an input element or onsubmit on a form:

function isAutofilled(element) {
  return [":autofill", ":-webkit-autofill", ":-moz-autofill"].some((selector) => {
    try {
      return element.matches(selector);
    } catch {
      return false;
    }
  });
}

To detect email for example, I tried "on change" and a mutation observer, neither worked. setInterval works well with LinkedIn auto-fill (not revealing all my code, but you get the idea) and it plays nice with the backend if you add extra conditions here to slow down the AJAX. And if there's no change in the form field, like they're not typing to edit their email, the lastEmail prevents pointless AJAX pings.

// lastEmail needs scope outside of setInterval for persistence.
var lastEmail = 'nobody';
window.setInterval(function() { // Auto-fill detection is hard.
    var theEmail = $("#email-input").val();
    if (
        ( theEmail.includes("@") ) &&
        ( theEmail != lastEmail )
    ) {
        lastEmail = theEmail;
        // Do some AJAX
    }
}, 1000); // Check the field every 1 second
Related