diff --git a/packages/clerk-js/src/ui/components/UserButton/UserButtonTrigger.tsx b/packages/clerk-js/src/ui/components/UserButton/UserButtonTrigger.tsx
index 7a99d4bb645..be5bcc3bc6e 100644
--- a/packages/clerk-js/src/ui/components/UserButton/UserButtonTrigger.tsx
+++ b/packages/clerk-js/src/ui/components/UserButton/UserButtonTrigger.tsx
@@ -42,6 +42,7 @@ export const UserButtonTrigger = withAvatarShimmer(
boxElementDescriptor={descriptors.userButtonAvatarBox}
imageElementDescriptor={descriptors.userButtonAvatarImage}
{...user}
+ key={`user-avatar-${user?.imageUrl || 'no-image'}`}
size={theme => theme.sizes.$7}
/>
diff --git a/packages/clerk-js/src/ui/elements/Avatar.tsx b/packages/clerk-js/src/ui/elements/Avatar.tsx
index 7f691fa032e..f1593a193f2 100644
--- a/packages/clerk-js/src/ui/elements/Avatar.tsx
+++ b/packages/clerk-js/src/ui/elements/Avatar.tsx
@@ -31,6 +31,10 @@ export const Avatar = (props: AvatarProps) => {
} = props;
const [error, setError] = React.useState(false);
+ React.useEffect(() => {
+ setError(false);
+ }, [imageUrl]);
+
const ImgOrFallback =
initials && (!imageUrl || error) ? (
diff --git a/packages/clerk-js/src/ui/elements/UserPreview.tsx b/packages/clerk-js/src/ui/elements/UserPreview.tsx
index 3d82d6e9dbd..3a1b3a36212 100644
--- a/packages/clerk-js/src/ui/elements/UserPreview.tsx
+++ b/packages/clerk-js/src/ui/elements/UserPreview.tsx
@@ -116,6 +116,7 @@ export const UserPreview = (props: UserPreviewProps) => {
{...samlAccount}
name={name}
avatarUrl={imageUrl}
+ key={`user-preview-avatar-${imageUrl || 'no-image'}`}
size={getAvatarSizes}
sx={avatarSx}
rounded={rounded}
diff --git a/packages/clerk-js/src/ui/elements/__tests__/Avatar.test.tsx b/packages/clerk-js/src/ui/elements/__tests__/Avatar.test.tsx
new file mode 100644
index 00000000000..49d7945ef10
--- /dev/null
+++ b/packages/clerk-js/src/ui/elements/__tests__/Avatar.test.tsx
@@ -0,0 +1,102 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import { Avatar } from '../Avatar';
+import { AppearanceProvider } from '../../customizables';
+
+const mockTheme = {
+ colors: {
+ $colorForeground: '#000000',
+ $colorBackground: '#ffffff',
+ },
+ radii: {
+ $circle: '50%',
+ $avatar: '4px',
+ },
+ sizes: {
+ $4: '16px',
+ $6: '24px',
+ },
+ space: {
+ $2: '8px',
+ },
+};
+
+const TestWrapper = ({ children }: { children: React.ReactNode }) => (
+
+ {children}
+
+);
+
+describe('Avatar', () => {
+ it('should reset error state when imageUrl changes', () => {
+ const { rerender } = render(
+
+
+ ,
+ );
+
+ // Initially should show image
+ expect(screen.getByAltText("Test User's logo")).toBeInTheDocument();
+
+ // Simulate image load error
+ const img = screen.getByAltText("Test User's logo");
+ img.dispatchEvent(new Event('error'));
+
+ // Should show initials after error
+ expect(screen.getByText('TU')).toBeInTheDocument();
+
+ // Change imageUrl - should reset error state and show new image
+ rerender(
+
+
+ ,
+ );
+
+ // Should show image again (error state reset)
+ expect(screen.getByAltText("Test User's logo")).toBeInTheDocument();
+ expect(screen.queryByText('TU')).not.toBeInTheDocument();
+ });
+
+ it('should handle null imageUrl properly', () => {
+ render(
+
+
+ ,
+ );
+
+ // Should show initials when no imageUrl
+ expect(screen.getByText('TU')).toBeInTheDocument();
+ });
+
+ it('should handle empty imageUrl properly', () => {
+ render(
+
+
+ ,
+ );
+
+ // Should show initials when empty imageUrl
+ expect(screen.getByText('TU')).toBeInTheDocument();
+ });
+});