Avatar

The Avatar component is a thumbnail representation of an entity, such as a user or an organization.

Usage

AvatarExample.tsx
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
<Flex items="end" gap="5" className="my-4">
<Avatar size="sm" alt="Avatar" />
<Avatar size="md" alt="Avatar" />
<Avatar size="lg" alt="Avatar" />
</Flex>
<Flex items="end" gap="5" className="my-4">
<Avatar size="sm" alt="Avatar" text="Ney" />
<Avatar size="md" alt="Avatar" text="Jake" />
<Avatar size="lg" alt="Avatar" text="MQ" />
</Flex>
<Flex items="end" gap="5" className="my-4">
<Avatar size="sm" alt="Avatar" text="Ney" src="/img/neytiri.webp" />
<Avatar size="md" alt="Avatar" text="Jake" src="/img/jake.webp" />
<Avatar size="lg" alt="Avatar" text="MQ" src="/img/miles-quaritch.png" />
</Flex>

Here's the <Avatar /> component in action.

Avatar
Avatar
Avatar
Ney
Jake
MQ
NeyAvatar
JakeAvatar
MQAvatar

Features

  • Requires an alt attribute for an accessible description.
  • Renders an avatar icon if no src or text prop is provided
  • Accepts a text prop to display a fallback text.
  • Accepts a src attribute defining the image URL.
  • Fallbacks to icon or text if image is not provided or fails to load.
  • Accepts a size prop to set the icon size.
  • Smooth image fade-in to avoid content flashing.

Source

avatar.tsx
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
  33. 33
  34. 34
  35. 35
  36. 36
  37. 37
  38. 38
  39. 39
  40. 40
  41. 41
  42. 42
  43. 43
  44. 44
  45. 45
  46. 46
  47. 47
  48. 48
  49. 49
  50. 50
  51. 51
  52. 52
  53. 53
  54. 54
  55. 55
  56. 56
  57. 57
  58. 58
  59. 59
  60. 60
  61. 61
import { type HTMLAttributes, forwardRef, type ImgHTMLAttributes, useRef, useEffect, useState } from 'react'
import { Icon } from '#app/components/media/icon'
import { cn } from '#app/utils/tailwind-merge.ts'
const AvatarRoot = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(({ ...props }, ref) => <div ref={ref} {...props} />)
AvatarRoot.displayName = 'AvatarRoot'
const AvatarImage = forwardRef<HTMLImageElement, ImgHTMLAttributes<HTMLImageElement>>(({ alt, ...props }, ref) => <img ref={ref} alt={alt} {...props} />)
AvatarImage.displayName = 'AvatarImage'
const AvatarText = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(({ ...props }, ref) => <span ref={ref} {...props} />)
AvatarText.displayName = 'AvatarText'
export type AvatarProps = HTMLAttributes<HTMLDivElement> & {
alt: string
src?: string
text?: string
size?: 'sm' | 'md' | 'lg'
}
const Avatar = forwardRef<HTMLDivElement, AvatarProps>(({ src, alt, text, size, className, ...props }, ref) => {
const [isLoaded, setIsLoaded] = useState(true) // Assume image loads before JavaScript.
const imageRef = useRef<HTMLImageElement>(null)
useEffect(() => {
// If JavaScript loads before image
if (imageRef.current) {
if (!imageRef.current.complete) {
// Image didn't complete loading, hide it and wait for the onLoad or onError event
setIsLoaded(false)
return
}
if (imageRef.current.naturalWidth === 0) {
// Image completed but is broken (https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/complete)
setIsLoaded(false)
}
}
}, [imageRef])
const onLoad = () => {
setIsLoaded(true)
}
const onError = () => {
setIsLoaded(false)
}
return (
<AvatarRoot ref={ref} className={cn('avatar', size, className)}>
{text ?
<AvatarText aria-label={alt}>{text}</AvatarText>
: <Icon name="avatar" label={alt} />}
{src ?
<AvatarImage ref={imageRef} src={src} alt={alt} onLoad={onLoad} onError={onError} className={`transition ease-in-out duration-300 ${isLoaded ? 'opacity-100' : 'opacity-0'}`} {...props} />
: null}
</AvatarRoot>
)
})
Avatar.displayName = 'Avatar'
export { Avatar }

Styling

tailwind.css
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
.avatar {
@apply bg-muted-400 relative flex items-center justify-center overflow-hidden rounded-full text-white;
/* Sizes */
&.sm {
@apply h-fluid-12 w-fluid-12;
> span {
@apply text-xs;
}
}
&.md {
@apply h-fluid-14 w-fluid-14;
> span {
@apply text-sm;
}
}
&.lg {
@apply h-fluid-15 w-fluid-15;
> span {
@apply text-md;
}
}
& > img {
@apply absolute min-h-full min-w-full object-cover;
}
& > span {
@apply absolute flex items-center justify-center;
}
}