<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Arthur Violy</title>
    <description>The latest articles on DEV Community by Arthur Violy (@_panpan).</description>
    <link>https://dev.to/_panpan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F13316%2Fd1600344-939c-4abd-8bd2-11908df4b012.png</url>
      <title>DEV Community: Arthur Violy</title>
      <link>https://dev.to/_panpan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/_panpan"/>
    <language>en</language>
    <item>
      <title>How a French word tricked Chrome into suggesting a credit card</title>
      <dc:creator>Arthur Violy</dc:creator>
      <pubDate>Thu, 24 Sep 2026 09:30:00 +0000</pubDate>
      <link>https://dev.to/_panpan/how-a-french-word-tricked-chrome-into-suggesting-a-credit-card-32p0</link>
      <guid>https://dev.to/_panpan/how-a-french-word-tricked-chrome-into-suggesting-a-credit-card-32p0</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;We had a plain numeric text field labeled &lt;strong&gt;"Numéro de commande"&lt;/strong&gt; (French for "Order number") in a form.&lt;br&gt;
Despite setting &lt;code&gt;autocomplete="off"&lt;/code&gt;, &lt;code&gt;inputmode="numeric"&lt;/code&gt;, and a &lt;code&gt;maxlength&lt;/code&gt;, Chrome (and Android's Autofill Manager, tied to Google Pay) kept suggesting the user's saved credit cards on that field.&lt;/p&gt;

&lt;p&gt;The fix that actually worked: &lt;strong&gt;breaking the word that triggers the heuristic&lt;/strong&gt; with an invisible Unicode character in the field's label, while leaving &lt;code&gt;autocomplete="off"&lt;/code&gt; in place as a first line of defense.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F07i1kzim8lemj5gcpyeb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F07i1kzim8lemj5gcpyeb.png" alt="Google Chrome numéro Autofill" width="623" height="300"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The bug report
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;On Chrome, tapping the "Numéro de commande" field&lt;br&gt;
opens Google Pay with the list of saved credit cards.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is a simplified version of the actual input, straight from the DOM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
  &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;
  &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Numéro de commande"&lt;/span&gt;
  &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Numéro de commande"&lt;/span&gt;
  &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"field__order_number"&lt;/span&gt;
  &lt;span class="na"&gt;autocomplete=&lt;/span&gt;&lt;span class="s"&gt;"off"&lt;/span&gt;
  &lt;span class="na"&gt;inputmode=&lt;/span&gt;&lt;span class="s"&gt;"numeric"&lt;/span&gt;
  &lt;span class="na"&gt;maxlength=&lt;/span&gt;&lt;span class="s"&gt;"9"&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing unusual here. It's a text input, autofill is explicitly disabled, and the only "special" thing is a numeric keyboard hint for mobile users. Yet Chrome and Android's Autofill Manager both treat it as a credit card number field.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why &lt;code&gt;autocomplete="off"&lt;/code&gt; doesn't help
&lt;/h2&gt;

&lt;p&gt;This isn't a bug, it's documented, deliberated behavior, on Chromium's part:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://issues.chromium.org/issues/41441398" rel="noopener noreferrer"&gt;Chromium issue tracker&lt;/a&gt; confirms Chrome deliberately ignores &lt;code&gt;autocomplete="off"&lt;/code&gt; for payment-related Autofill.&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://lists.w3.org/Archives/Public/public-whatwg-archive/2014Nov/0092.html" rel="noopener noreferrer"&gt;2014 WHATWG mailing list thread&lt;/a&gt;, with responses from Chromium engineers, explains the reasoning: &lt;code&gt;autocomplete="off"&lt;/code&gt; was so commonly misused on real checkout forms that Chrome decided not to trust it for payment and address fields at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short: Chrome's autofill heuristic for credit cards does &lt;strong&gt;not&lt;/strong&gt; rely solely on the &lt;code&gt;autocomplete&lt;/code&gt; attribute.&lt;br&gt;
It also looks at other signals: field type, numeric input mode, length constraints, and, as we found out, the &lt;strong&gt;label/placeholder text itself&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is also called out in &lt;a href="https://dev.to/atrandafir/chrome-provides-no-way-to-disable-credit-card-autofill-4415"&gt;Chrome provides no way to disable credit card autofill&lt;/a&gt;,&lt;br&gt;
which documents the same frustration from a different angle and confirms there's no clean way to opt out.&lt;/p&gt;
&lt;h2&gt;
  
  
  Finding the real trigger: the label
&lt;/h2&gt;

&lt;p&gt;Since &lt;code&gt;autocomplete="off"&lt;/code&gt; alone wasn't enough,&lt;br&gt;
we started testing which signal actually mattered.&lt;br&gt;
The field's &lt;code&gt;maxlength&lt;/code&gt; (8-9 digits) and &lt;code&gt;inputmode="numeric"&lt;/code&gt;&lt;br&gt;
looked like plausible suspects, matching typical card-number-ish constraints but the real breakthrough came from testing the &lt;strong&gt;label&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We temporarily changed the placeholder from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Numéro de commande
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Num éro de commande
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(a single, visible extra space &lt;code&gt;&lt;/code&gt; in the middle of "numéro"). The Google Pay suggestion strip disappeared immediately.&lt;/p&gt;

&lt;p&gt;That confirmed it: Chrome's (and Android's) credit-card-field classifier does some form of semantic matching against the label text, and "numéro" (French&amp;nbsp;for&amp;nbsp;"number") was scoring high enough, combined with the numeric input mode, to be classified as a card number field.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: an invisible character
&lt;/h2&gt;

&lt;p&gt;We used &lt;code&gt;U+200B&lt;/code&gt;, the &lt;strong&gt;Zero Width Space&lt;/strong&gt; (a character with no visual rendering that most screen readers silently skip):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Breaks up words in a label so Chrome/Android's credit-card-field&lt;/span&gt;
&lt;span class="c1"&gt;// heuristic (label-based, not just autocomplete-based) no longer&lt;/span&gt;
&lt;span class="c1"&gt;// recognizes a false-positive keyword, language-agnostic, no&lt;/span&gt;
&lt;span class="c1"&gt;// hardcoded keyword list needed.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;obfuscateLabelForAutofill&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;label&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;​&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;obfuscateLabelForAutofill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Numéro de commande&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// =&amp;gt; "Num​éro de comm​ande" (renders as "Numéro de commande")&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Applied to our field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
  &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;
  &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Num&amp;amp;#8203;éro de commande"&lt;/span&gt;
  &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Num&amp;amp;#8203;éro de commande"&lt;/span&gt;
  &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"field__order_number"&lt;/span&gt;
  &lt;span class="na"&gt;autocomplete=&lt;/span&gt;&lt;span class="s"&gt;"off"&lt;/span&gt;
  &lt;span class="na"&gt;inputmode=&lt;/span&gt;&lt;span class="s"&gt;"numeric"&lt;/span&gt;
  &lt;span class="na"&gt;maxlength=&lt;/span&gt;&lt;span class="s"&gt;"9"&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visually, nothing changes: the placeholder still reads "Numéro de commande".&lt;br&gt;
But Chrome's keyword matching no longer finds "numéro" as a contiguous string, and the autofill suggestion disappears.&lt;/p&gt;

&lt;h2&gt;
  
  
  A note on accessibility
&lt;/h2&gt;

&lt;p&gt;Because the label feeds both the &lt;code&gt;placeholder&lt;/code&gt; and &lt;code&gt;aria-label&lt;/code&gt; in our case,&lt;br&gt;
inserting a zero-width character mid-word raises an obvious question:&lt;br&gt;
does this confuse screen readers?&lt;/p&gt;

&lt;p&gt;We haven't done a proper investigation here: no testing across screen readers/browsers, no accessibility audit. Our assumption is that &lt;code&gt;U+200B&lt;/code&gt;&lt;br&gt;
is commonly treated as a non-rendering character with no phonetic value, so it &lt;em&gt;should&lt;/em&gt; be silently skipped rather than mispronounced or read out as a pause. But that's an assumption, not a verified result, and screen reader behavior around zero-width characters is known to vary depending on the engine, the browser, and even the surrounding characters.&lt;/p&gt;

&lt;p&gt;If you're considering reusing this trick, treat this as an open question rather than a solved one.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://issues.chromium.org/issues/41441398" rel="noopener noreferrer"&gt;Autofill credit card with autocomplete=off: Chromium issue tracker (#41441398)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lists.w3.org/Archives/Public/public-whatwg-archive/2014Nov/0092.html" rel="noopener noreferrer"&gt;PSA: Chrome ignoring autocomplete="off" for Autofill data: WHATWG mailing list, 2014&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/atrandafir/chrome-provides-no-way-to-disable-credit-card-autofill-4415"&gt;Chrome provides no way to disable credit card autofill: dev.to&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>chrome</category>
      <category>autofill</category>
      <category>webdev</category>
      <category>a11y</category>
    </item>
  </channel>
</rss>
