diff --git a/app/javascript/mastodon/components/__tests__/__snapshots__/avatar-test.jsx.snap b/app/javascript/mastodon/components/__tests__/__snapshots__/avatar-test.jsx.snap
index 2f0a2de324..124b50d8c7 100644
--- a/app/javascript/mastodon/components/__tests__/__snapshots__/avatar-test.jsx.snap
+++ b/app/javascript/mastodon/components/__tests__/__snapshots__/avatar-test.jsx.snap
@@ -2,7 +2,7 @@
 
 exports[`<Avatar /> Autoplay renders a animated avatar 1`] = `
 <div
-  className="account__avatar"
+  className="account__avatar account__avatar--loading"
   onMouseEnter={[Function]}
   onMouseLeave={[Function]}
   style={
@@ -14,6 +14,8 @@ exports[`<Avatar /> Autoplay renders a animated avatar 1`] = `
 >
   <img
     alt=""
+    onError={[Function]}
+    onLoad={[Function]}
     src="/animated/alice.gif"
   />
 </div>
@@ -21,7 +23,7 @@ exports[`<Avatar /> Autoplay renders a animated avatar 1`] = `
 
 exports[`<Avatar /> Still renders a still avatar 1`] = `
 <div
-  className="account__avatar"
+  className="account__avatar account__avatar--loading"
   onMouseEnter={[Function]}
   onMouseLeave={[Function]}
   style={
@@ -33,6 +35,8 @@ exports[`<Avatar /> Still renders a still avatar 1`] = `
 >
   <img
     alt=""
+    onError={[Function]}
+    onLoad={[Function]}
     src="/static/alice.jpg"
   />
 </div>
diff --git a/app/javascript/mastodon/components/avatar.tsx b/app/javascript/mastodon/components/avatar.tsx
index 8b16296c2c..f61d9676de 100644
--- a/app/javascript/mastodon/components/avatar.tsx
+++ b/app/javascript/mastodon/components/avatar.tsx
@@ -1,10 +1,11 @@
+import { useState, useCallback } from 'react';
+
 import classNames from 'classnames';
 
+import { useHovering } from 'mastodon/../hooks/useHovering';
+import { autoPlayGif } from 'mastodon/initial_state';
 import type { Account } from 'mastodon/models/account';
 
-import { useHovering } from '../../hooks/useHovering';
-import { autoPlayGif } from '../initial_state';
-
 interface Props {
   account: Account | undefined; // FIXME: remove `undefined` once we know for sure its always there
   size: number;
@@ -25,6 +26,8 @@ export const Avatar: React.FC<Props> = ({
   counterBorderColor,
 }) => {
   const { hovering, handleMouseEnter, handleMouseLeave } = useHovering(animate);
+  const [loading, setLoading] = useState(true);
+  const [error, setError] = useState(false);
 
   const style = {
     ...styleFromParent,
@@ -37,16 +40,28 @@ export const Avatar: React.FC<Props> = ({
       ? account?.get('avatar')
       : account?.get('avatar_static');
 
+  const handleLoad = useCallback(() => {
+    setLoading(false);
+  }, [setLoading]);
+
+  const handleError = useCallback(() => {
+    setError(true);
+  }, [setError]);
+
   return (
     <div
       className={classNames('account__avatar', {
-        'account__avatar-inline': inline,
+        'account__avatar--inline': inline,
+        'account__avatar--loading': loading,
       })}
       onMouseEnter={handleMouseEnter}
       onMouseLeave={handleMouseLeave}
       style={style}
     >
-      {src && <img src={src} alt='' />}
+      {src && !error && (
+        <img src={src} alt='' onLoad={handleLoad} onError={handleError} />
+      )}
+
       {counter && (
         <div
           className='account__avatar__counter'
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index f257368390..4ed05ead3f 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -2077,7 +2077,6 @@ body > [data-popper-placement] {
   display: block;
   position: relative;
   border-radius: var(--avatar-border-radius);
-  background-color: var(--surface-background-color);
 
   img {
     width: 100%;
@@ -2087,7 +2086,11 @@ body > [data-popper-placement] {
     display: inline-block; // to not show broken images
   }
 
-  &-inline {
+  &--loading {
+    background-color: var(--surface-background-color);
+  }
+
+  &--inline {
     display: inline-block;
     vertical-align: middle;
     margin-inline-end: 5px;