rapidUI

Avatar

User image with a text fallback, built on Base UI Avatar.

Installation

pnpm dlx shadcn@latest add https://rdf.dev.360online.com/r/avatar.json

Installs to @/components/ui/avatar.

Manual installation

Install the dependencies:

pnpm add @base-ui/react@^1.8.0

Copy the source into your project:

components/ui/avatar.tsx
'use client'

import { Avatar as AvatarPrimitive } from '@base-ui/react/avatar'
import type * as React from 'react'
import { cn } from '@/lib/utils'

function Avatar({
	className,
	size = 'default',
	...props
}: AvatarPrimitive.Root.Props & {
	size?: 'default' | 'sm' | 'lg'
}) {
	return (
		<AvatarPrimitive.Root
			data-slot="avatar"
			data-size={size}
			// The outline only frames photos; the initials fallback draws its own border
			className={cn(
				'group/avatar relative flex size-8 shrink-0 rounded-full select-none data-[size=lg]:size-10 data-[size=sm]:size-6 has-data-[slot=avatar-image]:after:pointer-events-none has-data-[slot=avatar-image]:after:absolute has-data-[slot=avatar-image]:after:inset-0 has-data-[slot=avatar-image]:after:rounded-full has-data-[slot=avatar-image]:after:border has-data-[slot=avatar-image]:after:border-border has-data-[slot=avatar-image]:after:mix-blend-darken dark:has-data-[slot=avatar-image]:after:mix-blend-lighten',
				className,
			)}
			{...props}
		/>
	)
}

function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {
	return (
		<AvatarPrimitive.Image
			data-slot="avatar-image"
			className={cn('aspect-square size-full rounded-full object-cover', className)}
			{...props}
		/>
	)
}

function AvatarFallback({ className, ...props }: AvatarPrimitive.Fallback.Props) {
	return (
		<AvatarPrimitive.Fallback
			data-slot="avatar-fallback"
			className={cn(
				'flex size-full items-center justify-center rounded-full border border-primary bg-secondary text-sm text-primary group-data-[size=sm]/avatar:text-xs',
				className,
			)}
			{...props}
		/>
	)
}

function AvatarBadge({ className, ...props }: React.ComponentProps<'span'>) {
	return (
		<span
			data-slot="avatar-badge"
			className={cn(
				'absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background select-none',
				'group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden',
				'group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2',
				'group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2',
				className,
			)}
			{...props}
		/>
	)
}

function AvatarGroup({ className, ...props }: React.ComponentProps<'div'>) {
	return (
		<div
			data-slot="avatar-group"
			className={cn(
				'group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background',
				className,
			)}
			{...props}
		/>
	)
}

function AvatarGroupCount({ className, ...props }: React.ComponentProps<'div'>) {
	return (
		<div
			data-slot="avatar-group-count"
			className={cn(
				'relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3',
				className,
			)}
			{...props}
		/>
	)
}

export { Avatar, AvatarImage, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarBadge }

A versatile avatar component for displaying user images with graceful fallbacks. Built on top of Base UI Avatar, it provides a fully accessible and customizable avatar implementation.

Features

  • Image-based avatars with automatic fallback while loading or on error
  • Three sizes via size: sm (24px), default (32px), lg (40px)
  • AvatarBadge for status dots or small icons in the bottom-right corner
  • AvatarGroup for overlapping stacks, with AvatarGroupCount for overflow
  • Group count adapts to the size of the avatars in the group
  • Custom styling via className

Usage

import {
  Avatar,
  AvatarBadge,
  AvatarFallback,
  AvatarGroup,
  AvatarGroupCount,
  AvatarImage,
} from '@/components/ui/avatar'

// Basic usage
export function OfficerAvatar() {
  return (
    <Avatar>
      <AvatarImage src="/avatars/anna-lindqvist.jpg" alt="Anna Lindqvist" />
      <AvatarFallback>AL</AvatarFallback>
    </Avatar>
  )
}

// Large with status badge
export function StatusAvatar() {
  return (
    <Avatar size="lg">
      <AvatarImage src="/avatars/ingrid-hansen.jpg" alt="Ingrid Hansen" />
      <AvatarFallback>IH</AvatarFallback>
      <AvatarBadge className="bg-primary" />
    </Avatar>
  )
}

// Group
export function CaseTeam() {
  return (
    <AvatarGroup>
      <Avatar>
        <AvatarFallback>AL</AvatarFallback>
      </Avatar>
      <Avatar>
        <AvatarFallback>OH</AvatarFallback>
      </Avatar>
      <AvatarGroupCount>+3</AvatarGroupCount>
    </AvatarGroup>
  )
}

Examples

Default

The handling officer on a case, shown with initials next to their name and role.

With Fallback

When the image is missing or fails to load, AvatarFallback shows the person’s initials instead.

Sizes

Avatars come in sm, default and lg. For other sizes, override with a size-* class.

With Badge

AvatarBadge renders a status dot; colour it with a theme token via className. Icons inside the badge are shown on default and lg avatars. Always pair the dot with a text status so the meaning doesn’t rely on colour alone.

Avatar Group

AvatarGroup overlaps avatars with a ring, and AvatarGroupCount shows the remaining count or an icon.

Custom Styles

A filled fallback, a ring marking the signed-in user, an icon for an organisation, and a custom size-14.

Subcomponents

  • Avatar - Root; size?: 'sm' | 'default' | 'lg' (default 'default')
  • AvatarImage - The image; hidden until it has loaded
  • AvatarFallback - Shown while the image loads or when it fails; delay (ms) postpones it to avoid a flash on fast connections
  • AvatarBadge - Status indicator placed inside Avatar
  • AvatarGroup - Overlapping container for multiple avatars
  • AvatarGroupCount - Overflow indicator placed last in AvatarGroup

Accessibility

  • Provide meaningful alt text on AvatarImage
  • Status conveyed by AvatarBadge color should also be available as text (e.g. a tooltip or visually hidden label)
  • Keep fallback text short and readable

Best Practices

  1. Image Usage:

    • Use appropriately sized images
    • Provide meaningful alt text
    • Handle image errors gracefully with a fallback
  2. Fallback Content:

    • Keep text short (1-2 characters)
    • Ensure readable contrast
  3. Sizing:

    • Use the built-in sizes for consistency
    • Keep a consistent size within a group
  4. Grouping:

    • Limit visible avatars and use AvatarGroupCount for the rest
  • Badge
  • Tooltip