[TextField] Styling bug when controlled component uses autoFill
Duplicates
- [X] I have searched the existing issues
Latest version
- [X] I have tested the latest version
Steps to reproduce 🕹
Link to live example:
Steps:
- Visit: https://d4yz1f.csb.app/? using a Chrome browser
- Fill in the 'CREATE PASSWORD HERE' section, submit the form, and save password in your browsers password manager 3.Reload page - observe the styling issue.
Current behavior 😯

Expected behavior 🤔
Should not have that overlap when autofill is present.
Here is the same app using @mui/[email protected] and the issue is not present: https://v1kf03.csb.app/?
Context 🔦
I upgraded from 5.9.3 to 5.11.12 and now my login page looks bad.
Your environment 🌎
See code sandbox.
I manually search through Mui versions to find that this bug appears to have been introduced between 5.10.11 and 5.10.12
I couldn't reproduce this issue on Firefox, mostly because I'm struggling with it to get the autofill to work.
Thanks for reporting this. I was able to reproduce the issue using the Material UI version you specified.
Thanks to this comment https://github.com/facebook/react/issues/1159#issuecomment-1025423604, I temporarily solved the problem and it may be helpful to you.
const [wasInitiallyAutofilled, setWasInitiallyAutofilled] = useState(false)
useLayoutEffect(
() => {
/**
* You can access chrome://flags/#unsafely-treat-insecure-origin-as-secure in your navigation bar
* and add your domain there for enable autofill in local development
*/
let autofilled = false
const checkAutofill = () => {
if (autofilled) return
const inputElements = document.getElementsByTagName('input')
if (!inputElements) return
for (let i = 0; i < inputElements.length; i++) {
const input = inputElements[i]
const isAutofilled = input.matches('*:-webkit-autofill')
if (isAutofilled) {
autofilled = true
break
}
}
setWasInitiallyAutofilled(autofilled)
}
// The time when it's ready is not very stable, so check few times
setTimeout(checkAutofill, 500)
setTimeout(checkAutofill, 1000)
setTimeout(checkAutofill, 2000)
},
[],
)
<Form>
<FormTextField
name="email"
focused={wasInitiallyAutofilled ? true : undefined}
...
/>
<FormPassword
name="password"
focused={wasInitiallyAutofilled ? true : undefined}
...
/>
</Form>
Not great, but a slightly better work-around than above, IMO:
// For re-usability, I made this a function that accepts a useState set function
// and returns a handler for each input to use, since you have at least two
// TextFields to deal with.
const makeAnimationStartHandler = (stateSetter) => (e) => {
const autofilled = !!e.target?.matches("*:-webkit-autofill");
if (e.animationName === "mui-auto-fill") {
stateSetter(autofilled);
}
if (e.animationName === "mui-auto-fill-cancel") {
stateSetter(autofilled);
}
};
...
<TextField
type="password"
id="password"
inputProps={{
onAnimationStart: makeAnimationStartHandler(setPasswordHasValue)
}}
InputLabelProps={{
shrink: passwordHasValue
}}
label="Password"
value={password}
onChange={(e) => {
setPassword(e.target.value);
...
}}
/>
https://stackoverflow.com/questions/76830737/chrome-autofill-causes-textbox-collision-for-textfield-label-and-value/76833254#76833254
Hi guys. I am new to this, but what I did find when I was busy with a form is that if there is a specific input that needs to be evaluated even when auto filled, and you can't find any solution yet, useEffect can help. When that specific value changes, whether filled or auto filled, it will get triggered.
This is happening to me and the @scarabaeus solution did not work unfortunately, but clearly, we have this bug in MUI 5 in our project, for those who have the auto-fill activated and only access the page that already fills all available fields.
Hi Ruffeng. I am not sure if you found any other solution yet... Check this out and let me know if this helps. You can also add a useEffect, so that when the page loads, the useEffect will trigger "updateInputClass()" without any prop. During updating your form, you can pass the input name on an 'onBlur' event. Here, i am using react hook forms that give access to errors and touchedFields. But you can propbably tweek it for your use.
` function itterateInputs() { const inputs = document.querySelectorAll("input"); inputs.forEach((input) => { input.value.length > 0 ? input.classList.add("filled-input") : input.classList.remove("filled-input"); }); }
function updateInputClass(inputName) { const input = inputName && document.getElementsByName(inputName)[0]; if (inputName) { if (!errors[inputName] && touchedFields[inputName]) { input.classList.add("filled-input"); } else if (errors[inputName]) { input.classList.remove("filled-input"); input.classList.add("error-input"); } else { input.classList.remove("filled-input"); } } else { itterateInputs(); } } `
You can also remove the error part in the code that I sent. I have found that the following code has been working for me, to add the "error-input" class to the input.
This is a custom input, so just check the className:
<FormInput {...formInputs[inputName]} register={register} onChange={handleInputChange} className={ errors[inputName] ? "error-input" : !errors[inputName] && touchedFields[inputName] ? "filled-input" : "" } errorMessage={errors[inputName]?.message} />
Is there an ETA on this , we are facing the same issue and will be a huge timesaver for us to have this fixed.
@DiegoAndai, I'm assigning this to you so you can prioritize it.
Looked into this issue today.
Bug explanation
There are two things that together cause the behavior:
-
Chrome only triggers
onChangefor the autofill after user interaction. This means that for controlled text fields, the controlled value mismatches the autofilled value, and is only synced after user interaction. This is why on the repro, the label moves after the user interaction. - Because of this change in
v5.10.12(PR),onEmptyandonFilledare redefined each timefilledchanges. This causescheckDirtyto recalculate again after eachonFilled, causing this effect to run.
Because said effect runs after onFilled, and it reads the mismatched controlled value (which is empty), filled is incorrectly reset to false.
Solutions
There's an immediate solution which is this change:
Click to toggle change
diff --git a/packages/mui-material/src/FormControl/FormControl.js b/packages/mui-material/src/FormControl/FormControl.js
index e9d5d01b77..590e7eaf18 100644
--- a/packages/mui-material/src/FormControl/FormControl.js
+++ b/packages/mui-material/src/FormControl/FormControl.js
@@ -192,6 +192,14 @@ const FormControl = React.forwardRef(function FormControl(inProps, ref) {
};
}
+ const onFilled = React.useCallback(() => {
+ setFilled(true);
+ }, []);
+
+ const onEmpty = React.useCallback(() => {
+ setFilled(false);
+ }, []);
+
const childContext = React.useMemo(() => {
return {
adornedStart,
@@ -207,12 +215,8 @@ const FormControl = React.forwardRef(function FormControl(inProps, ref) {
onBlur: () => {
setFocused(false);
},
- onEmpty: () => {
- setFilled(false);
- },
- onFilled: () => {
- setFilled(true);
- },
+ onEmpty,
+ onFilled,
onFocus: () => {
setFocused(true);
},
@@ -229,6 +233,8 @@ const FormControl = React.forwardRef(function FormControl(inProps, ref) {
focused,
fullWidth,
hiddenLabel,
+ onEmpty,
+ onFilled,
registerEffect,
required,
size,
But I think the correct solution will be to eventually refactor the autofill style implementation to rely on the :autofill CSS selector.
So I propose going for the temporal solution and creating an issue to implement this refactor in the future. What do you think @aarongarciah?
Other
A note on the repro
After user interaction the text field value shows as [object Object] because the onChange callback is incorrect:
<TextField
value={value}
- onChange={setValue}
+ onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
+ setValue(event.target.value);
+ }}
autoComplete="username"
id="outlined-basic"
label="username"
variant="outlined"
/>
Thanks for the help
Thanks @dwjohnston for pinning the exact version on which this was introduced, it was really helpful 👌🏼.
@DiegoAndai makes sense, the temporal solution is an improvement already.
Any updates on this ?
any information?
Sorry for the delay, I'll try to get a PR for this next week.
For the people experiencing this issue, may I ask you to test with https://github.com/mui/material-ui/pull/44135's build and check if the issue is fixed? You can do so by doing the following on your project's package.json:
"@mui/material": "https://pkg.csb.dev/mui/material-ui/commit/6b32256d/@mui/material",
For the people experiencing this issue, may I ask you to test with #44135 build and check if the issue is fixed? You can do so by doing the following on your project's
package.json:"@mui/material": "https://pkg.csb.dev/mui/material-ui/commit/6b32256d/@mui/material",
For me, it solves the issue. However, the input field's value only updates when the Chrome tab is in focus. This means the textField's value is not set until the user clicks on the website.
This issue has been closed. If you have a similar problem but not exactly the same, please open a new issue. Now, if you have additional information related to this issue or things that could help future readers, feel free to leave a comment.
[!NOTE] @dwjohnston How did we do? Your experience with our support team matters to us. If you have a moment, please share your thoughts in this short Support Satisfaction survey.
The fix has been merged and will be out in the next release (>6.4.0)
This means the textField's value is not set until the user clicks on the website.
We tried to fix this in https://github.com/mui/material-ui/pull/44135 but ultimately it seems to be a limitation on Chrome in which there's no way of accessing the value before user interaction (see https://github.com/mui/material-ui/pull/44135#issuecomment-2593134886).
If anyone figures out a way to improve this, I'll gladly review it 😊