Repository navigation
Guidance for disabling browser autofill in data-entry forms #2258
Replies: 2 comments
|
Since this is browser/password-manager behavior rather than anything TanStack Form controls, the reliability ceiling is set by Chrome and Safari's heuristics, not the form library, but there's a more consistent pattern than plain
None of this is airtight across every browser/password-manager combination, that limit is real and worth documenting as-is rather than promising a fix, per-field |
|
Here is a battle-tested pattern for disabling browser and password-manager autofill in internal data-entry applications built with TanStack Form. Because modern browser heuristics (especially Chrome and Safari) aggressively override Reusable Field Component PatternBelow is a reusable TanStack Form field component that wraps the standard input with autofill defense strategies: import * as React from 'react'
import { FieldApi } from '@tanstack/react-form'
interface DataEntryInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
// Pass the TanStack Form field API directly
field: FieldApi<any, any, any, any>
label: string
// Field type category to apply the right autofill defense
entryType?: 'text' | 'address' | 'password' | 'email' | 'search'
}
export function DataEntryInput({
field,
label,
entryType = 'text',
id,
type = 'text',
className,
...rest
}: DataEntryInputProps) {
// 1. Generate a stable but unique DOM ID per component instance to prevent cross-record heuristic matching
const uniqueId = React.useId()
const domId = id ?? `field-${field.name.replace(/\./g, '-')}-${uniqueId}`
// 2. Select the optimal autocomplete defense strategy per field category
const resolveAutoComplete = (): string => {
switch (entryType) {
case 'password':
// Modern password managers ignore "off", but respect "new-password"
return 'new-password'
case 'address':
case 'email':
case 'text':
// Non-standard tokens like "one-time-code" or "off" prevent address heuristic matching
return 'off'
case 'search':
return 'off'
default:
return 'off'
}
}
// 3. Optional ReadOnly Guard: Prevents browsers from auto-populating fields on initial page load
const [isReady, setIsReady] = React.useState(false)
return (
<div className="flex flex-col gap-1.5">
<label htmlFor={domId} className="text-sm font-medium text-slate-700">
{label}
</label>
<input
id={domId}
name={field.name}
type={type}
value={field.state.value ?? ''}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
// Multi-level autofill suppression:
autoComplete={resolveAutoComplete()}
autoCorrect="off"
autoCapitalize="off"
spellCheck={false}
data-form-type="other"
data-lpignore="true" // 1Password / LastPass ignore hint
data-1p-ignore="true"
// Prevent autofill before operator interaction
readOnly={!isReady}
onFocus={() => setIsReady(true)}
className={`px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${className ?? ''}`}
{...rest}
/>
{field.state.meta.errors ? (
<span className="text-xs text-red-500">{field.state.meta.errors.join(', ')}</span>
) : null}
</div>
)
}Usage in TanStack FormWhen assembling your data entry form: import { useForm } from '@tanstack/react-form'
import { DataEntryInput } from './DataEntryInput'
export function OperatorPatientForm({ recordId }: { recordId: string }) {
const form = useForm({
defaultValues: {
patientName: '',
homeAddress: '',
notes: '',
accessPin: '',
},
onSubmit: async ({ value }) => {
// submit handler
},
})
return (
<form
autoComplete="off"
onSubmit={(e) => {
e.preventDefault()
e.stopPropagation()
form.handleSubmit()
}}
className="space-y-4"
>
{/* Hidden dummy fields to consume early browser autofill traps */}
<input type="text" style={{ display: 'none' }} tabIndex={-1} aria-hidden="true" />
<input type="password" style={{ display: 'none' }} tabIndex={-1} aria-hidden="true" />
<form.Field name="patientName">
{(field) => (
<DataEntryInput
field={field}
label="Patient Full Name"
entryType="text"
/>
)}
</form.Field>
<form.Field name="homeAddress">
{(field) => (
<DataEntryInput
field={field}
label="Street Address"
entryType="address"
/>
)}
</form.Field>
<form.Field name="accessPin">
{(field) => (
<DataEntryInput
field={field}
label="Security PIN / Password"
type="password"
entryType="password"
/>
)}
</form.Field>
<button type="submit" className="btn-primary">
Save Record
</button>
</form>
)
}Summary of Controlled Behaviors & Inherent Browser Limits
|
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
I’m using TanStack Form for an internal data-entry app. One operator enters records for many different people on the same browser. Chrome and Safari may suggest the operator’s saved address, password managers attach to password fields, and normal inputs offer values from previous records. That makes it easy to put one person’s data into another record.
Today I add
autocomplete="off"to the form and individual inputs:This is inconsistent across browsers and password managers, and easy to miss in reusable field components.
Since TanStack Form is headless, I’m not expecting it to override browser behavior. Could the docs include a recommended best-effort pattern for this use case, covering address, password, and ordinary text fields, plus the limits that cannot be controlled? A small example using shared field components would be enough.
All reactions