Skip to content
GRUDGED Free audit
§ Field notes for web developers ·

Your Password Manager Keeps Hijacking a Field That Isn't a Password

Password managers pop a 'save password?' prompt on ordinary text inputs — even with autocomplete=off. Why it happens, and the contenteditable fix that stops it.

You’re building a chat box, a command bar, an inline cell editor — some plain text-entry surface that has nothing to do with credentials. And the operating system or browser keeps popping its “save this password?” tooltip right over it, or worse, autofilling someone’s saved login into it.

So you do the obvious thing: autocomplete="off". It keeps happening. You try autocomplete="new-password", then renaming the field, then aria tweaks, then a hidden decoy input. Some of it helps in one browser and breaks in another. You are now losing an afternoon to a save-password tooltip.

Why autocomplete=“off” doesn’t save you

Password managers stopped trusting autocomplete="off" years ago — on purpose. Sites used it for a long time to block managers from working, which was bad for users (it pushed people toward weak, memorable passwords). So browsers and OS-level managers (macOS/iCloud Passwords, Chrome, 1Password, and friends) now deliberately ignore autocomplete="off" on anything that smells login-shaped, and fall back to heuristics: the field’s name/id, its type, nearby labels, whether it sits in a <form>, what’s next to it.

A text input named something like secret, key, token, pin, or login — or just one sitting inside a form near a button — can trip those heuristics. You can keep fighting them, but it’s a fight you’re structurally set up to lose, because the heuristics are intentionally adversarial to “don’t autocomplete me.”

The fix: stop using a form field

The reliable escape is to not give the password manager an <input> to latch onto at all. Use a contenteditable element. A <div contenteditable> is not a form control, so managers have no hook — no tooltip, no autofill, ever, in any browser.

<div contenteditable="true" role="textbox" aria-multiline="true"
     data-placeholder="Type a message…" class="composer"></div>

It gives you a stable, fully styleable text surface. The trade is that you now own a few behaviors a real <input> gave you for free. Here’s the real-world checklist:

  • Read/write the value with el.innerText (or textContent) — there is no .value.
  • Placeholder doesn’t exist as an attribute — fake it in CSS: .composer:empty::before { content: attr(data-placeholder); color: #888; }.
  • Submit on Enter: handle keydownEnter without Shift → preventDefault() and submit; Shift+Enter → let the newline through.
  • Paste arrives as HTML. Intercept it and insert plain text, or a pasted webpage will drag its formatting into your box:
    el.addEventListener('paste', (e) => {
      e.preventDefault();
      const text = e.clipboardData.getData('text/plain');
      document.execCommand('insertText', false, text); // still works everywhere
    });
  • Caret control: focus() works; to drop the caret at the end, use a Range + Selection rather than hoping.
  • Accessibility: add role="textbox", aria-multiline where relevant, and a real label — you’ve left the world where the browser does this for you.

When to actually reach for this

Only when a genuine <input>/<textarea> can’t be made to behave: chat composers, command palettes, inline editors, search-as-you-type bars that managers keep mistaking for logins. For real login and signup forms you want the password manager — don’t break it there. This is the tool for the in-app text surfaces that aren’t credentials but keep getting treated like they are.

Further reading


I’m Chris Moore — an independent developer and consultant in Henderson, NV, working under Grudged LLC. I build and rescue the operational systems small teams run on. If the browser is fighting your UI, get in touch.

← All field notes Talk to Chris Moore →