Material-ui: [TextField] Handle Chrome autofill

Created on 6 Feb 2019  ·  57Comments  ·  Source: mui-org/material-ui

There is a display issue in the TextField outlined variant when chrome pre-fills the text box on initial page load. The auto filled text overwrites the label. See below screenshot.
screenshot from 2019-02-06 08-37-05

Also notice the yellow background for auto filled text, can it be overridden?

MUI version 3.9.2

bug 🐛 TextField important

Most helpful comment

Also notice the yellow background for auto filled text, can it be overridden?

@garygrubb I did this via a theme override:

const theme = createMuiTheme({
  overrides: {
    MuiInputBase: {
      input: {
        '&:-webkit-autofill': {
          transitionDelay: '9999s',
          transitionProperty: 'background-color, color',
        },
      },
    },
  },
});

Not ideal, but it's something.

The alternative is to use a box-shadow to fill the input: https://stackoverflow.com/questions/2781549/removing-input-background-colour-for-chrome-autocomplete

All 57 comments

This need to fix this problem! #14453 might provide some ways to address the problem.
@garygrubb I'm wondering, are you able to create a reproduction example that we can consistently use to solve the problem? Thank you! This looks very close to the Google main login form:

capture d ecran 2019-02-08 a 18 18 40

Sorry for the delayed response. I will try and create a small reproduction when time permits later this week. In the mean time I have a temporary fix in place - setting autoFocus on any one of the Textfields solves the issue.

`                            <TextField
                                required
                                **autoFocus**
                                variant="outlined"
                                id="username"
                                label="mobile number"
                                defaultValue=""
                                margin="normal"
                                name="username"
                                type="tel"
                                onChange={this.handleChange}
                                error={!this.state.validMobile}
                                autoComplete="tel-national username"
                            />

setting autoFocus on any one of the Textfields solves the issue.

Doesn't seem to work for me.

Also notice the yellow background for auto filled text, can it be overridden?

@garygrubb I did this via a theme override:

const theme = createMuiTheme({
  overrides: {
    MuiInputBase: {
      input: {
        '&:-webkit-autofill': {
          transitionDelay: '9999s',
          transitionProperty: 'background-color, color',
        },
      },
    },
  },
});

Not ideal, but it's something.

The alternative is to use a box-shadow to fill the input: https://stackoverflow.com/questions/2781549/removing-input-background-colour-for-chrome-autocomplete

@oliviertassinari This might do the trick: https://stackoverflow.com/a/41530164/396889

@MarkMurphy Any idea how I could reproduce the problem?

@MarkMurphy Any idea how I could reproduce the problem?

For me, I created a simple username and password form and allowed Chrome to remember my credentials.

Using Chrome 72.0.3626.119 on MacOS High Sierra 10.13.6

@material-ui/core version "3.8.1"
@material-ui/styles version "3.0.0-alpha.6"

@MarkMurphy I could reproduce it with https://material-ui.com/getting-started/page-layout-examples/sign-in/:

feb-27-2019 13-13-39

This is with the text field standard variant. So, we can notice two issues:

  1. The focus active state isn't detected, we have a fix for it in #14132, just waiting for someone to lead the effort.
  2. The fill state is only detected when moving the focus to the page.

Let's try with the outlined variant now. We can use this page: https://deploy-preview-14499--material-ui.netlify.com/getting-started/page-layout-examples/sign-in-side/

feb-27-2019 13-24-10

It seems like its a chrome autofill problem. If we were provided with ways to detect chrome's autofill it'd be an easy fix... but Im not aware of any solution. Only solution was to turn off autocomplete/autofill for all textfields.

@ymoon715 there’s at least one other workable solution which I posted a link to above.

Workaround documentation:

input:-webkit-autofill,
.my-class:-webkit-autofill {
    -webkit-transition-delay: 9999999s;
}

Thanks @cezarderevlean , I got rid of the yellow background, Now just need to figure out how to remove the greyed out helper text.
Screenshot from 2019-04-22 11-14-45

If you don't want to "disable" autofill as described in the first link I've put above, keep in mind that after autofill, it will appear like in your screenshot only when page refreshed by a code change in
development (as far as I've tested). A normal browser load/refresh will be fine, it will make the placeholder shrink.

Also, if what I've said above proves untrue, one workaround you can implement is always keep the placeholder shrinked with this prop on TextField:

InputLabelProps={{
    shrink: true,
}}

@cezarderevlean thanks. You are right. A normal browser refresh works fine.

I don't know how I missed it but @MarkMurphy had a similar solution way back & it uses material theme override which is a bit cleaner for use case. Thanks Mark.

If you don't want to "disable" autofill as described in the first link I've put above, keep in mind that after autofill, it will appear like in your screenshot only when page refreshed by a code change in
development (as far as I've tested). A normal browser load/refresh will be fine, it will make the placeholder shrink.

Also, if what I've said above proves untrue, one workaround you can implement is always keep the placeholder shrinked with this prop on TextField:

InputLabelProps={{
    shrink: true,
}}

Thanks, this worked for me.

Setting autoFocus on the username field and autoComplete='new-password' on the password field also did the trick for me.

Update: I guess I jumped the gun here. My solution below seems to only work for plain react -- not MUI. Any ideas why?

For folks wanting to get the actual values of the input, instead of just detecting the presence of the values (see https://github.com/mui-org/material-ui/issues/14427#issuecomment-466054906), I've come up with a solution detailed in this SO article: https://stackoverflow.com/questions/35049555/chrome-autofill-autocomplete-no-value-for-password/56157489#56157489. The link has a good history of this overall issue that affects Chrome in general.

  componentDidMount() {
    var evt = new MouseEvent("click", {
      view: window,
      clientX: 0
    });
    window.dispatchEvent(evt);
    let iterations = 0;

    const interval = setInterval(() => {
      const value = this.inputs.email.value;
      // plain js alternative:
      // const value = document.getElementById("email").value;
      if (!!value || iterations > 20) {
        console.log(value);
        return clearInterval(interval);
      }

      iterations++;
      console.log("not found -> repeat");
    }, 100);
  }

Basically, I force a mouse click on the input so Chrome sets the value on the input. Then at a later point when the value is ready, I grab the value. It's definitely hacky, but Chrome leaves us with no options... (see details of Chrome's stance in the SO article).

TL;DR: This is a browser issue. They do not consistently fire input events when autofilling forms.

This is an issue at the browser level. If I click inside the page and then reload it correctly dispatches input events. If I click on the page, click in the navbar it only stops working on the second enter. If I land on the page by copying the link to the page and entering it in the navbar it also works.

I'm not even sure we can fix this for all browsers since when polled the input value is still empty even if autofilled. There's a neat trick for chrome only. Will investigate if only chrome has these issues.

@eps1lon Have you opened a bug in Chromium if this is Chromium related?

This does not work for me: https://github.com/mui-org/material-ui/issues/718#issuecomment-529064043.
This does work: InputLabelProps={{ shrink: true }}, although it always stays shrinked

The following diff seems to solve the issue:

diff --git a/packages/material-ui/src/InputBase/InputBase.js b/packages/material-ui/src/InputBase/InputBase.js
index d258e5b71d..a7aebb1288 100644
--- a/packages/material-ui/src/InputBase/InputBase.js
+++ b/packages/material-ui/src/InputBase/InputBase.js
@@ -311,6 +311,10 @@ const InputBase = React.forwardRef(function InputBase(props, ref) {
     }
   };

+  // Check the input state on mount, in case it was filled by the user
+  // or auto filled by the browser before the hydration (for ssr).
+  React.useEffect(() => {
+    checkDirty(inputRef.current);
+  }, []);
+
   const handleClick = event => {
     if (inputRef.current && event.currentTarget === event.target) {
       inputRef.current.focus();

Can anyone confirm the quality of the fix?

@oliviertassinari Having a working component in material ui is needed, but if this is a Chromium bug as @eps1lon mentioned above, this workaround fix should be only temporarily, and the issue should be fixed within Chromium. Can you confirm this is a bug in Chromium or is just a MUI bug?

If it is a Chromium bug we should check if there is a bug opened in chromium for this and if not open it? Something like: chromium search result

(https://github.com/mui-org/material-ui/issues/14427#issuecomment-524535710)

I don't think that it's an issue with Chrome at this point. I think that it's an issue with React and server-side rendering. If a user writes content in an input, before React hydrate, we are never notified about it: https://github.com/facebook/react/issues/12955

@oliviertassinari But the server side rendering is not happening in create-react-app development server?

The issue is most likely that any onInput or onChange handlers need to be set via javascript. If this happens after chrome autofills these events are lost. The more javascript the more likely you loose the events. React started an effort to reduce the time events are fired without react catching those. This will help hydrated UIs.

The other issue is that sometimes input.value isn't set although the value is autofilled. Only after focusing/blurring the input which likely has unintended side-effects (e.g. form validation).

It's very hard to properly research this issue since almost no reports have a reproducible example. We should not apply any fix until we have identified the issue properly though theoretically the fix from @oliviertassinari https://github.com/mui-org/material-ui/issues/14427#issuecomment-530145849 looks like it should at least cover dropped events.

@eps1lon Interesting. My proposed diff focuses on the only issue I could reproduce on https://material-ui.com/getting-started/page-layout-examples/sign-in/ (in dev mode, in my local env). I would need to run a last test (synchronously listen to the change event) to confirm that the "event triggered before hydration" is the root of the problem. I think that it's worth handling, at least until React show some interest in https://github.com/facebook/react/issues/12955

@croraf How do you reproduce the issue with CRA?

I believe that the "event triggered before hydration" problem also affects us in https://github.com/mui-org/material-ui/issues/14132#issuecomment-453657016.

@oliviertassinari CRA is irrelevant here, but the following code can be used with CRA (maybe this codesandbox even uses CRA internally): https://codesandbox.io/s/textinput-bug-hsv9m

A) To reproduce it in this codesandbox:
1) open this sandbox's app preview in new window.
2) enter and save credentials (after Chrome dialog asks to save them)
3) refresh the window to see the issue (as Chrome will apply autofill). The issue does not appear consistently on refresh, but with double clicking F5 (fast double refresh) it is reproduced almost consistently.

B) Also I found something similar
1) In the codesandbox app preview (not in separate app window).
2) Fill and submit the form (so that Chrome stores username/password combination)
3) Reload just the app preview. The form won't be autofilled immediately. (in material-ui sandbox you can just delete the autofilled content of the fields)
4) Click on username textfield. Chrome will offer you to pick a user.
5) Hover the user and see the issue happening on password field.

image

@eps1lon Says the issue can be that Chrome fires autofill related event(s) before handlers are set up.

But how can Chrome even autofill before React creates the input fileds in the DOM, and how React create them in the DOM before javascript has run enough to set up event handlers?

This seems fishy to me, and can maybe happen with SSR (although I don't know how that works). But CRA development server does not use SSR.

@eps1lon Says the issue can be that Chrome fires autofill related event(s) before handlers are set up.

This only applies to static html which is hydrated by react and was only theoretically constructed because we didn't have a reproduction at the moment. Now that we have we can investigate actual issues.

I can't reproduce the issue on Chrome v76.0.3809.132, macOS v10.14.6.
I can reproduce part of the issue on Chrome v76.0.3809.87, Windows 10.

I can reproduce the issue on same setting, Chrome v76.0.3809.132, macOS v10.14.6.

my solution:

import { isChrome, osName} from "react-device-detect";
.....
.....

  const isChromeOSX = osName === 'Mac OS' && isChrome

  const [shrink, setShrink] = useState(isChromeOSX ? true : undefined)

  useEffect(() => {
    if(isChromeOSX) {
      const listen = () => {
        setShrink(undefined)
        window.removeEventListener('click',listen)
      }
      window.addEventListener('click', listen);
      return () => {
        window.removeEventListener('click',listen)
      };
    }
  }, [])

  return  <TextField
          .....
          variant="outlined"
          InputLabelProps={{ shrink }}
        />

The following diff solves the occurrences of the problem I can reproduce, can somebody confirm it?

diff --git a/packages/material-ui/src/FormControl/FormControl.js b/packages/material-ui/src/FormControl/FormControl.js
index 0ede7ca404..c7cb22aeb9 100644
--- a/packages/material-ui/src/FormControl/FormControl.js
+++ b/packages/material-ui/src/FormControl/FormControl.js
@@ -145,6 +145,14 @@ const FormControl = React.forwardRef(function FormControl(props, ref) {
     };
   }

+  const onFilled = React.useCallback(() => {
+    setFilled(true);
+  }, []);
+
+  const onEmpty = React.useCallback(() => {
+    setFilled(false);
+  }, []);
+
   const childContext = {
     adornedStart,
     disabled,
@@ -156,16 +164,8 @@ const FormControl = React.forwardRef(function FormControl(props, ref) {
     onBlur: () => {
       setFocused(false);
     },
-    onEmpty: () => {
-      if (filled) {
-        setFilled(false);
-      }
-    },
-    onFilled: () => {
-      if (!filled) {
-        setFilled(true);
-      }
-    },
+    onEmpty,
+    onFilled,
     onFocus: () => {
       setFocused(true);
     },
diff --git a/packages/material-ui/src/InputBase/InputBase.js b/packages/material-ui/src/InputBase/InputBase.js
index d258e5b71d..9cb6c94f43 100644
--- a/packages/material-ui/src/InputBase/InputBase.js
+++ b/packages/material-ui/src/InputBase/InputBase.js
@@ -116,6 +116,13 @@ export const styles = theme => {
       '&$disabled': {
         opacity: 1, // Reset iOS opacity
       },
+      '&:-webkit-autofill': {
+        animationDuration: '5000s',
+        animationName: '$auto-fill',
+      },
+    },
+    '@keyframes auto-fill': {
+      from: {},
     },
     /* Styles applied to the `input` element if `margin="dense"`. */
     inputMarginDense: {
@@ -239,17 +246,20 @@ const InputBase = React.forwardRef(function InputBase(props, ref) {
     }
   }, [muiFormControl, disabled, focused, onBlur]);

+  const onFilled = muiFormControl && muiFormControl.onFilled;
+  const onEmpty = muiFormControl && muiFormControl.onEmpty;
+
   const checkDirty = React.useCallback(
     obj => {
       if (isFilled(obj)) {
-        if (muiFormControl && muiFormControl.onFilled) {
-          muiFormControl.onFilled();
+        if (onFilled) {
+          onFilled();
         }
-      } else if (muiFormControl && muiFormControl.onEmpty) {
-        muiFormControl.onEmpty();
+      } else if (onEmpty) {
+        onEmpty();
       }
     },
-    [muiFormControl],
+    [onFilled, onEmpty],
   );

   useEnhancedEffect(() => {
@@ -311,6 +321,12 @@ const InputBase = React.forwardRef(function InputBase(props, ref) {
     }
   };

+  // Check the input state on mount, in case it was filled by the user
+  // or auto filled by the browser before the hydration (for SSR).
+  React.useEffect(() => {
+    checkDirty(inputRef.current);
+  }, []); // eslint-disable-line react-hooks/exhaustive-deps
+
   const handleClick = event => {
     if (inputRef.current && event.currentTarget === event.target) {
       inputRef.current.focus();
@@ -354,6 +370,10 @@ const InputBase = React.forwardRef(function InputBase(props, ref) {
     };
   }

+  const handleAutoFill = () => {
+    // Provide a fake value as Chrome might not let you access it for security reasons.
+    checkDirty({ value: 'x' });
+  };
+
   return (
     <div
       className={clsx(
@@ -399,6 +419,7 @@ const InputBase = React.forwardRef(function InputBase(props, ref) {
           defaultValue={defaultValue}
           disabled={fcs.disabled}
           id={id}
+          onAnimationStart={handleAutoFill}
           name={name}
           onBlur={handleBlur}
           onChange={handleChange}

One part is inspired by https://github.com/tocco/tocco-client/commit/cb3b9b59994380f17c6650ffe4b63f96948072b6.

I tested this fix locally with material-ui sandbox (the docs page) and cannot reproduce the issue when I apply it.
This was the issue:
image


With this fix I can neither reproduce the similar issue mentioned in part B of https://github.com/mui-org/material-ui/issues/14427#issuecomment-530503051
The one seen here: image

@oliviertassinari @eps1lon I tested v4.4.3 in my business application and I didn't see the error. So it seems the patch (although maybe not the most elegant) solved the issue and I removed the hack to always have labels raised (InputLabelProps={{ shrink: true }} which was not really a good hack, as the labels were rised even if the input fields were empty).

@oliviertassinari @eps1lon
I do have a minor new issue now unfortunately. When playing with autofill. Select user, then delete password, then try to select again (or something like that) the labels stay rised, even though the fields are empty :/
image
->
image

Chrome: 77
Ubuntu: 18.04

@oliviertassinari @eps1lon @croraf I've also tested it in my application ans the issue is no longer present. Thanks for your help.

@garygrubb Try to see if you notice the subtle error I'm mentioning in the above post.

@coraf Yes I was able to reproduce the same issue on desktop Chrome Version 76.0.3809.132 (Official Build) (64-bit). The mobile version and firefox seem ok.
If you open a new issue for this, please tag me so I can follow along.

Would the following help?

diff --git a/packages/material-ui/src/InputBase/InputBase.js b/packages/material-ui/src/InputBase/InputBase.js
index 439e8afa7..f0ab8bb66 100644
--- a/packages/material-ui/src/InputBase/InputBase.js
+++ b/packages/material-ui/src/InputBase/InputBase.js
@@ -373,6 +373,12 @@ const InputBase = React.forwardRef(function InputBase(props, ref) {
   }

   const handleAutoFill = () => {
+    // The change event is correcty triggered when the input is focused.
+    // There is no need to detect autofill.
+    if (fcs.focused) {
+      return;
+    }
+
     // Provide a fake value as Chrome might not let you access it for security reasons.
     checkDirty({ value: 'x' });
   };

@oliviertassinari OK, I found a consistent reproduction of the remaining issue on mui docs page.
NOTE: This remaining issue is really a minor thing, and probably not that high prio, comparing to what the original issue was.

  1. Make chrome store credentials (username and password) for localhost:3000
  2. Refresh the http://localhost:3000/components/text-fields#outlined
    I get something like:
    image

  3. Delete the email field manually but the password is still autofilled
    image

  4. Click on the email field. Chrome will show you a dropdown with saved users for that page (localhost:3000)
  5. Hover over one of the offered users in the dropdown but don't select it. Chrome will show you the preview of that user selection in the fields.
  6. click away from the dropdown to close it.

You will get this situation:
image

@oliviertassinari Your additional fix seems to solve the remaining issue I described.

But I have found another flavor of the issue :D
The reproduction steps are similar as above but instead in step 3 also manually clear the password:

  1. Delete both the username and password fields

This will give you the following resultant issue at the end
image

This will give you the following resultant issue at the end

@croraf Oh boy, I haven't seen this one coming! Yeah, it makes sense.

diff --git a/packages/material-ui/src/InputBase/InputBase.js b/packages/material-ui/src/InputBase/InputBase.js
index 763ab105c..9dce66f9d 100644
--- a/packages/material-ui/src/InputBase/InputBase.js
+++ b/packages/material-ui/src/InputBase/InputBase.js
@@ -86,6 +86,7 @@ export const styles = theme => {
       // Make the flex item shrink with Firefox
       minWidth: 0,
       width: '100%', // Fix IE 11 width issue
+      animationName: '$auto-fill-cancel',
       '&::-webkit-input-placeholder': placeholder,
       '&::-moz-placeholder': placeholder, // Firefox 19+
       '&:-ms-input-placeholder': placeholder, // IE 11
@@ -123,6 +124,9 @@ export const styles = theme => {
     '@keyframes auto-fill': {
       from: {},
     },
+    '@keyframes auto-fill-cancel': {
+      from: {},
+    },
     /* Styles applied to the `input` element if `margin="dense"`. */
     inputMarginDense: {
       paddingTop: 4 - 1,
@@ -380,9 +384,11 @@ const InputBase = React.forwardRef(function InputBase(props, ref) {
     };
   }

-  const handleAutoFill = () => {
+  const handleAutoFill = event => {
     // Provide a fake value as Chrome might not let you access it for security reasons.
-    checkDirty({ value: 'x' });
+    checkDirty(
+      event.animationName.indexOf('auto-fill-cancel') !== -1 ? inputRef.current : { value: 'x' },
+    );
   };

   return (

From:

4.4.2
Sep-23-2019 22-48-19

4.4.3
Sep-23-2019 22-48-26

proposed patch
Sep-23-2019 22-47-06

@oliviertassinari , @croraf - Tested in production and it works as expected. Thanks.
Please tag me in any other bug fixes you would like me to test in poduction. I'd be happy to help.

Happy to hear it.

Doesn't support dark theme?

unsupported

@MAkerboom We could consider changing the color to be a darker blue. Is this someting you would like to experiment with?

@oliviertassinari as long as the outline is showing and it still has enough contrast with the text is seems fine by me

@MAkerboom The best approach I can come up with is:

diff --git a/packages/material-ui/src/FilledInput/FilledInput.js b/packages/material-ui/src/FilledInput/FilledInput.js
index 71d0bcc94..4b681cba1 100644
--- a/packages/material-ui/src/FilledInput/FilledInput.js
+++ b/packages/material-ui/src/FilledInput/FilledInput.js
@@ -106,6 +106,12 @@ export const styles = theme => {
     /* Styles applied to the `input` element. */
     input: {
       padding: '27px 12px 10px',
+      '&:-webkit-autofill': {
+        WebkitBoxShadow: theme.palette.type === 'dark' ? '0 0 0 100px #266798 inset' : null,
+        WebkitTextFillColor: theme.palette.type === 'dark' ? '#fff' : null,
+        borderTopLeftRadius: 'inherit',
+        borderTopRightRadius: 'inherit',
+      },
     },
     /* Styles applied to the `input` element if `margin="dense"`. */
     inputMarginDense: {
diff --git a/packages/material-ui/src/OutlinedInput/OutlinedInput.js b/packages/material-ui/src/OutlinedInput/OutlinedInput.js
index f60c4e5d5..376e37ba8 100644
--- a/packages/material-ui/src/OutlinedInput/OutlinedInput.js
+++ b/packages/material-ui/src/OutlinedInput/OutlinedInput.js
@@ -65,6 +65,11 @@ export const styles = theme => {
     /* Styles applied to the `input` element. */
     input: {
       padding: '18.5px 14px',
+      '&:-webkit-autofill': {
+        WebkitBoxShadow: theme.palette.type === 'dark' ? '0 0 0 100px #266798 inset' : null,
+        WebkitTextFillColor: theme.palette.type === 'dark' ? '#fff' : null,
+        borderRadius: theme.shape.borderRadius,
+      },
     },
     /* Styles applied to the `input` element if `margin="dense"`. */
     inputMarginDense: {

Is that OK with you? Do you want to submit a pull request? :)

Capture d’écran 2019-10-13 à 12 10 55
Capture d’écran 2019-10-13 à 12 11 12

Much better! thx. will try to make a PR
test akerboom app_(Pixel 2)

I saw this bug while using <AutoComplete/>. The text field would not "shrink" its label when focusing on it. I saw this in Firefox and Chrome (did not test other browsers). Upgrading from v4.5.0 to v4.5.2 fixed the issue. I believe the PRs linked to this issue are responsible, so thank you!

FWIW here is a theme override to prevent chrome from displaying custom colors on autofilled inputs :

theme = {
    overrides: {
        MuiInputBase: {
            root: {
                fontFamily: '"Lato", serif',
                "& input": {
                    "&:-webkit-autofill": {
                        transition:
                            "background-color 50000s ease-in-out 0s, color 50000s ease-in-out 0s",
                    },
                    "&:-webkit-autofill:focus": {
                        transition:
                            "background-color 50000s ease-in-out 0s, color 50000s ease-in-out 0s",
                    },
                    "&:-webkit-autofill:hover": {
                        transition:
                            "background-color 50000s ease-in-out 0s, color 50000s ease-in-out 0s",
                    },
                },
            },
        },
    }
}

@armellarcier Thanks for sharing. I would be careful with removing the custom colors, I think that it's meant to help users. It could also be interesting from a security standpoint, to better know if you are on the right domain name, not being fished (otherwise autofill doesn't work)

Was this page helpful?
0 / 5 - 0 ratings