diff --git a/docs/pages/api/avatar.md b/docs/pages/api/avatar.md
index 6db035aa37a66e..603306698672cc 100644
--- a/docs/pages/api/avatar.md
+++ b/docs/pages/api/avatar.md
@@ -28,7 +28,7 @@ You can learn more about the difference by [reading this guide](/guides/minimizi
| children | node | | Used to render icon or text elements inside the Avatar if `src` is not set. This can be an element, or just a string. |
| classes | object | | Override or extend the styles applied to the component. See [CSS API](#css) below for more details. |
| component | elementType | 'div' | The component used for the root node. Either a string to use a DOM element or a component. |
-| imgProps | object | | Attributes applied to the `img` element if the component is used to display an image. |
+| imgProps | object | | Attributes applied to the `img` element if the component is used to display an image. It can be used to listen for the loading error event. |
| sizes | string | | The `sizes` attribute for the `img` element. |
| src | string | | The `src` attribute for the `img` element. |
| srcSet | string | | The `srcSet` attribute for the `img` element. Use this attribute for responsive image display. |
@@ -46,11 +46,12 @@ Any other props supplied will be provided to the root element (native element).
| Rule name | Global class | Description |
|:-----|:-------------|:------------|
| root | .MuiAvatar-root | Styles applied to the root element.
-| colorDefault | .MuiAvatar-colorDefault | Styles applied to the root element if there are children and not `src` or `srcSet`.
+| colorDefault | .MuiAvatar-colorDefault | Styles applied to the root element if not `src` or `srcSet`.
| circle | .MuiAvatar-circle | Styles applied to the root element if `variant="circle"`.
| rounded | .MuiAvatar-rounded | Styles applied to the root element if `variant="rounded"`.
| square | .MuiAvatar-square | Styles applied to the root element if `variant="square"`.
| img | .MuiAvatar-img | Styles applied to the img element if either `src` or `srcSet` is defined.
+| fallback | .MuiAvatar-fallback | Styles applied to the fallback icon
You can override the style of the component thanks to one of these customization points:
diff --git a/docs/src/pages/components/avatars/FallbackAvatars.js b/docs/src/pages/components/avatars/FallbackAvatars.js
new file mode 100644
index 00000000000000..808c5d3e812632
--- /dev/null
+++ b/docs/src/pages/components/avatars/FallbackAvatars.js
@@ -0,0 +1,31 @@
+import React from 'react';
+import { makeStyles } from '@material-ui/core/styles';
+import Avatar from '@material-ui/core/Avatar';
+import { deepOrange } from '@material-ui/core/colors';
+
+const useStyles = makeStyles(theme => ({
+ root: {
+ display: 'flex',
+ '& > *': {
+ margin: theme.spacing(1),
+ },
+ },
+ orange: {
+ color: theme.palette.getContrastText(deepOrange[500]),
+ backgroundColor: deepOrange[500],
+ },
+}));
+
+export default function FallbackAvatars() {
+ const classes = useStyles();
+
+ return (
+
+ );
+}
diff --git a/docs/src/pages/components/avatars/FallbackAvatars.tsx b/docs/src/pages/components/avatars/FallbackAvatars.tsx
new file mode 100644
index 00000000000000..174e0ee9cb2e96
--- /dev/null
+++ b/docs/src/pages/components/avatars/FallbackAvatars.tsx
@@ -0,0 +1,33 @@
+import React from 'react';
+import { makeStyles, createStyles, Theme } from '@material-ui/core/styles';
+import Avatar from '@material-ui/core/Avatar';
+import { deepOrange } from '@material-ui/core/colors';
+
+const useStyles = makeStyles((theme: Theme) =>
+ createStyles({
+ root: {
+ display: 'flex',
+ '& > *': {
+ margin: theme.spacing(1),
+ },
+ },
+ orange: {
+ color: theme.palette.getContrastText(deepOrange[500]),
+ backgroundColor: deepOrange[500],
+ },
+ }),
+);
+
+export default function FallbackAvatars() {
+ const classes = useStyles();
+
+ return (
+
+ );
+}
diff --git a/docs/src/pages/components/avatars/avatars.md b/docs/src/pages/components/avatars/avatars.md
index 6cd169f3f056b5..a3cfa8fc434772 100644
--- a/docs/src/pages/components/avatars/avatars.md
+++ b/docs/src/pages/components/avatars/avatars.md
@@ -30,3 +30,13 @@ Icon avatars are created by passing an icon as `children`.
If you need square or rounded avatars, use the `variant` prop.
{{"demo": "pages/components/avatars/VariantAvatars.js"}}
+
+## Fallbacks
+
+The component fallbacks if there is an error loading the avatar image, in this order, to:
+
+- the provided children
+- the first letter of tha `alt` text
+- a generic avatar icon
+
+{{"demo": "pages/components/avatars/FallbackAvatars.js"}}
diff --git a/packages/material-ui/src/Avatar/Avatar.js b/packages/material-ui/src/Avatar/Avatar.js
index 95347e73d6bd61..9505a20f6ba73b 100644
--- a/packages/material-ui/src/Avatar/Avatar.js
+++ b/packages/material-ui/src/Avatar/Avatar.js
@@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import withStyles from '../styles/withStyles';
+import Person from '../internal/svg-icons/Person';
export const styles = theme => ({
/* Styles applied to the root element. */
@@ -20,7 +21,7 @@ export const styles = theme => ({
overflow: 'hidden',
userSelect: 'none',
},
- /* Styles applied to the root element if there are children and not `src` or `srcSet`. */
+ /* Styles applied to the root element if not `src` or `srcSet`. */
colorDefault: {
color: theme.palette.background.default,
backgroundColor:
@@ -43,9 +44,55 @@ export const styles = theme => ({
textAlign: 'center',
// Handle non-square image. The property isn't supported by IE 11.
objectFit: 'cover',
+ // Hide alt text.
+ color: 'transparent',
+ // Same color as the Skeleton.
+ backgroundColor: theme.palette.action.hover,
+ // Hide the image broken icon, only works on Chrome.
+ textIndent: 10000,
+ },
+ /* Styles applied to the fallback icon */
+ fallback: {
+ width: '75%',
+ height: '75%',
},
});
+function useLoaded({ src, srcSet }) {
+ const [loaded, setLoaded] = React.useState(false);
+
+ React.useEffect(() => {
+ if (!src && !srcSet) {
+ return undefined;
+ }
+
+ setLoaded(false);
+
+ let active = true;
+ const image = new Image();
+ image.src = src;
+ image.srcSet = srcSet;
+ image.onload = () => {
+ if (!active) {
+ return;
+ }
+ setLoaded('loaded');
+ };
+ image.onerror = () => {
+ if (!active) {
+ return;
+ }
+ setLoaded('error');
+ };
+
+ return () => {
+ active = false;
+ };
+ }, [src, srcSet]);
+
+ return loaded;
+}
+
const Avatar = React.forwardRef(function Avatar(props, ref) {
const {
alt,
@@ -62,9 +109,13 @@ const Avatar = React.forwardRef(function Avatar(props, ref) {
} = props;
let children = null;
- const img = src || srcSet;
- if (img) {
+ // Use a hook instead of onError on the img element to support server-side rendering.
+ const loaded = useLoaded({ src, srcSet });
+ const hasImg = src || srcSet;
+ const hasImgNotFailing = hasImg && loaded !== 'error';
+
+ if (hasImgNotFailing) {
children = (
);
- } else {
+ } else if (childrenProp != null) {
children = childrenProp;
+ } else if (hasImg && alt) {
+ children = alt[0];
+ } else {
+ children = ;
}
return (
@@ -86,7 +141,7 @@ const Avatar = React.forwardRef(function Avatar(props, ref) {
classes.system,
classes[variant],
{
- [classes.colorDefault]: !img,
+ [classes.colorDefault]: !hasImgNotFailing,
},
className,
)}
@@ -124,8 +179,8 @@ Avatar.propTypes = {
*/
component: PropTypes.elementType,
/**
- * Attributes applied to the `img` element if the component
- * is used to display an image.
+ * Attributes applied to the `img` element if the component is used to display an image.
+ * It can be used to listen for the loading error event.
*/
imgProps: PropTypes.object,
/**
diff --git a/packages/material-ui/src/internal/svg-icons/Person.js b/packages/material-ui/src/internal/svg-icons/Person.js
new file mode 100644
index 00000000000000..43d267a1af70ef
--- /dev/null
+++ b/packages/material-ui/src/internal/svg-icons/Person.js
@@ -0,0 +1,10 @@
+import React from 'react';
+import createSvgIcon from './createSvgIcon';
+
+/**
+ * @ignore - internal component.
+ */
+export default createSvgIcon(
+ ,
+ 'Person',
+);
diff --git a/test/utils/createDOM.js b/test/utils/createDOM.js
index 3b9a8cedf71aa7..4e0abf622a598d 100644
--- a/test/utils/createDOM.js
+++ b/test/utils/createDOM.js
@@ -6,6 +6,7 @@ const whitelist = [
// required for fake getComputedStyle
'CSSStyleDeclaration',
'Element',
+ 'Image',
'HTMLElement',
'HTMLInputElement',
'Performance',