From e5eedde7a4cc8be04894d36911253ae66d5b6898 Mon Sep 17 00:00:00 2001
From: Muhammad Umer Aamir <93091750+umeraamir69@users.noreply.github.com>
Date: Thu, 22 Oct 2026 10:53:28 -0400
Subject: [PATCH] Set autocomplete on SignIn and SignUp identifier fields.
Email, phone, username, and name fields had no autocomplete tokens, so Safari and password managers would not offer the user's details.
---
.changeset/signin-signup-autocomplete.md | 5 ++
.../ui/src/components/SignIn/SignInStart.tsx | 7 ++-
.../SignIn/__tests__/SignInStart.test.tsx | 50 +++++++++++++++++++
.../ui/src/components/SignUp/SignUpForm.tsx | 5 ++
.../SignUp/__tests__/SignUpStart.test.tsx | 15 ++++++
5 files changed, 81 insertions(+), 1 deletion(-)
create mode 100644 .changeset/signin-signup-autocomplete.md
diff --git a/.changeset/signin-signup-autocomplete.md b/.changeset/signin-signup-autocomplete.md
new file mode 100644
index 00000000000..f40a4920307
--- /dev/null
+++ b/.changeset/signin-signup-autocomplete.md
@@ -0,0 +1,5 @@
+---
+'@clerk/ui': patch
+---
+
+Sign-in and sign-up identifier fields now set standard `autocomplete` tokens so browsers and password managers can offer email, name, and phone.
diff --git a/packages/ui/src/components/SignIn/SignInStart.tsx b/packages/ui/src/components/SignIn/SignInStart.tsx
index 4e3ae9bf6ff..3a42ad78fa4 100644
--- a/packages/ui/src/components/SignIn/SignInStart.tsx
+++ b/packages/ui/src/components/SignIn/SignInStart.tsx
@@ -57,6 +57,11 @@ import {
getSignUpAttributeFromIdentifier,
} from './utils';
+function identifierAutoComplete(identifier: SignInStartIdentifier, webauthn: boolean): string {
+ const token = identifier === 'phone_number' ? 'tel' : identifier === 'email_address' ? 'email' : 'username';
+ return webauthn ? `${token} webauthn` : token;
+}
+
const useAutoFillPasskey = () => {
const [isSupported, setIsSupported] = useState(false);
const { navigate } = useRouter();
@@ -671,7 +676,7 @@ function SignInStartInternal(): JSX.Element {
onActionClicked={switchToNextIdentifier}
{...identifierFieldProps}
autoFocus={shouldAutofocus}
- autoComplete={isWebAuthnAutofillSupported ? 'webauthn' : undefined}
+ autoComplete={identifierAutoComplete(identifierAttribute, isWebAuthnAutofillSupported)}
isLastAuthenticationStrategy={isIdentifierLastAuthenticationStrategy}
/>
diff --git a/packages/ui/src/components/SignIn/__tests__/SignInStart.test.tsx b/packages/ui/src/components/SignIn/__tests__/SignInStart.test.tsx
index 1da263fbd58..365659b65e7 100644
--- a/packages/ui/src/components/SignIn/__tests__/SignInStart.test.tsx
+++ b/packages/ui/src/components/SignIn/__tests__/SignInStart.test.tsx
@@ -76,6 +76,37 @@ describe('SignInStart', () => {
screen.getByText(/email address/i);
});
+ it('sets autocomplete=email on an email-only identifier field', async () => {
+ const { wrapper } = await createFixtures(f => {
+ f.withEmailAddress();
+ });
+
+ render(, { wrapper });
+ expect(screen.getByRole('textbox', { name: /email address/i })).toHaveAttribute('autocomplete', 'email');
+ });
+
+ it('sets autocomplete=username when email and username are both enabled', async () => {
+ const { wrapper } = await createFixtures(f => {
+ f.withEmailAddress();
+ f.withUsername();
+ });
+
+ render(, { wrapper });
+ expect(screen.getByRole('textbox', { name: /email address or username/i })).toHaveAttribute(
+ 'autocomplete',
+ 'username',
+ );
+ });
+
+ it('sets autocomplete=tel on a phone identifier field', async () => {
+ const { wrapper } = await createFixtures(f => {
+ f.withPhoneNumber();
+ });
+
+ render(, { wrapper });
+ expect(screen.getByRole('textbox', { name: /phone number/i })).toHaveAttribute('autocomplete', 'tel');
+ });
+
it('enables login with username', async () => {
const { wrapper } = await createFixtures(f => {
f.withUsername();
@@ -131,6 +162,25 @@ describe('SignInStart', () => {
screen.getByText('Use passkey instead');
});
+ it('puts webauthn after the field name when passkey autofill is on', async () => {
+ const { wrapper } = await createFixtures(f => {
+ f.withEmailAddress();
+ f.withPasskey();
+ f.withPasskeySettings({
+ allow_autofill: true,
+ show_sign_in_button: false,
+ });
+ });
+
+ render(, { wrapper });
+ await waitFor(() => {
+ expect(screen.getByRole('textbox', { name: /email address/i })).toHaveAttribute(
+ 'autocomplete',
+ 'email webauthn',
+ );
+ });
+ });
+
it('enables login with passkey via autofill', async () => {
const { wrapper, fixtures } = await createFixtures(f => {
f.withEmailAddress();
diff --git a/packages/ui/src/components/SignUp/SignUpForm.tsx b/packages/ui/src/components/SignUp/SignUpForm.tsx
index e36a979e464..649b1817c46 100644
--- a/packages/ui/src/components/SignUp/SignUpForm.tsx
+++ b/packages/ui/src/components/SignUp/SignUpForm.tsx
@@ -59,6 +59,7 @@ export const SignUpForm = (props: SignUpFormProps) => {
{...formState.firstName.props}
isRequired={fields.firstName?.required}
isOptional={!fields.firstName?.required}
+ autoComplete='given-name'
/>
)}
{shouldShow('lastName') && (
@@ -66,6 +67,7 @@ export const SignUpForm = (props: SignUpFormProps) => {
{...formState.lastName.props}
isRequired={fields.lastName?.required}
isOptional={!fields.lastName?.required}
+ autoComplete='family-name'
/>
)}
@@ -76,6 +78,7 @@ export const SignUpForm = (props: SignUpFormProps) => {
{...formState.username.props}
isRequired={fields.username?.required}
isOptional={!fields.username?.required}
+ autoComplete='username'
/>
)}
@@ -86,6 +89,7 @@ export const SignUpForm = (props: SignUpFormProps) => {
isRequired={fields.emailAddress?.required}
isOptional={!fields.emailAddress?.required}
isDisabled={fields.emailAddress?.disabled}
+ autoComplete='email'
actionLabel={canToggleEmailPhone ? localizationKeys('signUp.start.actionLink__use_phone') : undefined}
onActionClicked={canToggleEmailPhone ? () => handleEmailPhoneToggle('phoneNumber') : undefined}
/>
@@ -97,6 +101,7 @@ export const SignUpForm = (props: SignUpFormProps) => {
{...formState.phoneNumber.props}
isRequired={fields.phoneNumber?.required}
isOptional={!fields.phoneNumber?.required}
+ autoComplete='tel'
actionLabel={canToggleEmailPhone ? localizationKeys('signUp.start.actionLink__use_email') : undefined}
onActionClicked={canToggleEmailPhone ? () => handleEmailPhoneToggle('emailAddress') : undefined}
/>
diff --git a/packages/ui/src/components/SignUp/__tests__/SignUpStart.test.tsx b/packages/ui/src/components/SignUp/__tests__/SignUpStart.test.tsx
index eb5fb3f4739..f7adfc1457b 100644
--- a/packages/ui/src/components/SignUp/__tests__/SignUpStart.test.tsx
+++ b/packages/ui/src/components/SignUp/__tests__/SignUpStart.test.tsx
@@ -31,6 +31,21 @@ describe('SignUpStart', () => {
screen.getByText('Email address');
});
+ it('sets standard autocomplete tokens on sign-up fields', async () => {
+ const { wrapper } = await createFixtures(f => {
+ f.withEmailAddress({ required: true });
+ f.withPhoneNumber({ required: true });
+ f.withUsername({ required: true });
+ f.withName({ required: true });
+ });
+ render(, { wrapper });
+ expect(screen.getByRole('textbox', { name: 'First name' })).toHaveAttribute('autocomplete', 'given-name');
+ expect(screen.getByRole('textbox', { name: 'Last name' })).toHaveAttribute('autocomplete', 'family-name');
+ expect(screen.getByLabelText('Email address', { selector: 'input' })).toHaveAttribute('autocomplete', 'email');
+ expect(screen.getByLabelText('Phone number', { selector: 'input' })).toHaveAttribute('autocomplete', 'tel');
+ expect(screen.getByRole('textbox', { name: 'Username' })).toHaveAttribute('autocomplete', 'username');
+ });
+
it('enables sign up with phone number', async () => {
const { wrapper } = await createFixtures(f => {
f.withPhoneNumber({ required: true });