Skip to content

Instantly share code, notes, and snippets.

@ielijose
Created January 6, 2026 23:28
Show Gist options
  • Select an option

  • Save ielijose/44ab518e39fd1c71c7e2e27b1dc753d4 to your computer and use it in GitHub Desktop.

Select an option

Save ielijose/44ab518e39fd1c71c7e2e27b1dc753d4 to your computer and use it in GitHub Desktop.
React Hook Form - Browser Autofill Detection using CSS Animations
// React Hook Form - Browser Autofill Detection
// Uses CSS animations to detect when browser autofills inputs
"use client";
import { useEffect, useRef, forwardRef, InputHTMLAttributes } from "react";
import { useForm, UseFormRegisterReturn, UseFormSetValue, Path } from "react-hook-form";
// =============================================================================
// CSS - Add this to your global CSS or inject via style tag
// =============================================================================
const autofillCSS = `
@keyframes onAutoFillStart { from {} to {} }
@keyframes onAutoFillCancel { from {} to {} }
input:-webkit-autofill {
animation-name: onAutoFillStart;
}
input:not(:-webkit-autofill) {
animation-name: onAutoFillCancel;
}
`;
// =============================================================================
// Option 1: Hook-based approach
// =============================================================================
function useAutofillDetection<T extends Record<string, unknown>>(
name: Path<T>,
setValue: UseFormSetValue<T>
) {
const ref = useRef<HTMLInputElement>(null);
useEffect(() => {
const input = ref.current;
if (!input) return;
const handleAnimationStart = (e: AnimationEvent) => {
if (e.animationName === "onAutoFillStart") {
requestAnimationFrame(() => {
setValue(name, input.value as T[typeof name], {
shouldValidate: true,
shouldDirty: true,
});
});
}
};
input.addEventListener("animationstart", handleAnimationStart);
return () => input.removeEventListener("animationstart", handleAnimationStart);
}, [name, setValue]);
return ref;
}
// Helper to merge refs
const mergeRefs = <T,>(
...refs: (React.Ref<T> | undefined)[]
): React.RefCallback<T> => {
return (element) => {
refs.forEach((ref) => {
if (typeof ref === "function") {
ref(element);
} else if (ref && "current" in ref) {
(ref as React.MutableRefObject<T | null>).current = element;
}
});
};
};
// Usage with hook
function LoginFormWithHook() {
const { register, handleSubmit, setValue, formState: { errors } } = useForm<{
email: string;
password: string;
}>();
const emailRef = useAutofillDetection("email", setValue);
const passwordRef = useAutofillDetection("password", setValue);
return (
<>
<style dangerouslySetInnerHTML={{ __html: autofillCSS }} />
<form onSubmit={handleSubmit(console.log)}>
<input
type="email"
autoComplete="email"
{...register("email", { required: "Email is required" })}
ref={mergeRefs(emailRef, register("email").ref)}
/>
<input
type="password"
autoComplete="current-password"
{...register("password", { required: "Password is required" })}
ref={mergeRefs(passwordRef, register("password").ref)}
/>
<button type="submit">Login</button>
</form>
</>
);
}
// =============================================================================
// Option 2: Component-based approach (cleaner)
// =============================================================================
interface AutofillInputProps extends InputHTMLAttributes<HTMLInputElement> {
registration: UseFormRegisterReturn;
onAutofill?: (value: string) => void;
}
const AutofillInput = forwardRef<HTMLInputElement, AutofillInputProps>(
({ registration, onAutofill, ...props }, _ref) => {
const internalRef = useRef<HTMLInputElement>(null);
useEffect(() => {
const input = internalRef.current;
if (!input || !onAutofill) return;
const handleAnimationStart = (e: AnimationEvent) => {
if (e.animationName === "onAutoFillStart") {
requestAnimationFrame(() => {
onAutofill(input.value);
});
}
};
input.addEventListener("animationstart", handleAnimationStart);
return () => input.removeEventListener("animationstart", handleAnimationStart);
}, [onAutofill]);
return (
<input
{...props}
{...registration}
ref={(element) => {
internalRef.current = element;
registration.ref(element);
}}
/>
);
}
);
AutofillInput.displayName = "AutofillInput";
// Usage with component
function LoginFormWithComponent() {
const { register, handleSubmit, setValue } = useForm<{
email: string;
password: string;
}>();
return (
<>
<style dangerouslySetInnerHTML={{ __html: autofillCSS }} />
<form onSubmit={handleSubmit(console.log)}>
<AutofillInput
type="email"
autoComplete="email"
registration={register("email", { required: true })}
onAutofill={(value) => setValue("email", value, { shouldValidate: true })}
/>
<AutofillInput
type="password"
autoComplete="current-password"
registration={register("password", { required: true })}
onAutofill={(value) => setValue("password", value, { shouldValidate: true })}
/>
<button type="submit">Login</button>
</form>
</>
);
}
export { useAutofillDetection, AutofillInput, mergeRefs, autofillCSS };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment