UIPackage

Avatar

React data-display
Edit on GitHub

Round or rounded-square user image with a fallback that shows initials or an icon when the image is missing or fails to load. Sizes from xs to 2xl, optional status dot, and a group composition for stacked avatar lists.

Also available for Vue ->

Installation

$ npx shadcn@latest add https://react.uipkge.dev/r/react/avatar.json

Or with the named registry: npx shadcn@latest add @uipkge-react/avatar

Examples

Props

Name Type / Values Default Required
size
'xs''sm''default''lg''xl''2xl'
default optional
rounded
'none''sm''default''md''lg''xl''2xl''3xl''full'
default optional
color
'default''primary''secondary''destructive''success''warning''info''error''muted'
default optional
variant
'default''outlined''soft'
optional
tile boolean false optional
disabled boolean false optional
loading boolean false optional

Dependencies

Used by

Files (3)

  • components/ui/avatar/avatar.tsx 5.1 kB
    'use client'
    
    import * as React from 'react'
    import * as AvatarPrimitive from '@radix-ui/react-avatar'
    import { cn } from '@/lib/utils'
    import { avatarVariants, avatarFallbackVariants } from './avatar.variants'
    
    /* ------------------------------------------------------------------ */
    /* Avatar (Root)                                                       */
    /* ------------------------------------------------------------------ */
    
    export interface AvatarProps
      extends Omit<React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>, 'color'> {
      size?: 'xs' | 'sm' | 'default' | 'lg' | 'xl' | '2xl'
      rounded?: 'none' | 'sm' | 'default' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'full'
      color?: 'default' | 'primary' | 'secondary' | 'destructive' | 'success' | 'warning' | 'info' | 'error' | 'muted'
      variant?: 'default' | 'outlined' | 'soft'
      tile?: boolean
      disabled?: boolean
      loading?: boolean
    }
    
    const Avatar = React.forwardRef<React.ElementRef<typeof AvatarPrimitive.Root>, AvatarProps>(
      ({ className, size, rounded, color, variant, tile = false, disabled = false, loading = false, ...props }, ref) => (
        <AvatarPrimitive.Root
          ref={ref}
          data-uipkge=""
          data-slot="avatar"
          className={cn(
            avatarVariants({ size, rounded, color, variant }),
            tile ? 'rounded-none' : '',
            disabled ? 'opacity-50 cursor-not-allowed' : '',
            loading ? 'animate-pulse' : '',
            className,
          )}
          {...props}
        />
      ),
    )
    Avatar.displayName = 'Avatar'
    
    /* ------------------------------------------------------------------ */
    /* AvatarImage                                                         */
    /* ------------------------------------------------------------------ */
    
    const AvatarImage = React.forwardRef<
      React.ElementRef<typeof AvatarPrimitive.Image>,
      React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
    >(({ className, ...props }, ref) => (
      <AvatarPrimitive.Image
        ref={ref}
        data-uipkge=""
        data-slot="avatar-image"
        className={cn('aspect-square size-full object-cover', className)}
        {...props}
      />
    ))
    AvatarImage.displayName = 'AvatarImage'
    
    /* ------------------------------------------------------------------ */
    /* AvatarFallback                                                      */
    /* ------------------------------------------------------------------ */
    
    export interface AvatarFallbackProps
      extends Omit<React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>, 'color'> {
      size?: 'xs' | 'sm' | 'default' | 'lg' | 'xl' | '2xl'
      color?: 'default' | 'primary' | 'secondary' | 'destructive' | 'success' | 'warning' | 'info' | 'error' | 'muted'
      text?: string
    }
    
    const AvatarFallback = React.forwardRef<
      React.ElementRef<typeof AvatarPrimitive.Fallback>,
      AvatarFallbackProps
    >(({ className, size = 'default', color = 'default', text, children, ...props }, ref) => (
      <AvatarPrimitive.Fallback
        ref={ref}
        data-uipkge=""
        data-slot="avatar-fallback"
        className={cn(avatarFallbackVariants({ size, color }), className)}
        {...props}
      >
        {text ?? children}
      </AvatarPrimitive.Fallback>
    ))
    AvatarFallback.displayName = 'AvatarFallback'
    
    /* ------------------------------------------------------------------ */
    /* AvatarGroup — custom layout wrapper (not a Radix primitive)         */
    /* ------------------------------------------------------------------ */
    
    const overflowSizeClasses: Record<NonNullable<AvatarGroupProps['size']>, string> = {
      xs: 'size-4 text-[8px]',
      sm: 'size-6 text-xs',
      default: 'size-8 text-sm',
      lg: 'size-12 text-base',
      xl: 'size-16 text-lg',
      '2xl': 'size-20 text-xl',
    }
    
    export interface AvatarGroupProps extends React.HTMLAttributes<HTMLDivElement> {
      max?: number
      overlap?: boolean
      size?: 'xs' | 'sm' | 'default' | 'lg' | 'xl' | '2xl'
      total?: number
      /** Custom overflow indicator. Receives the hidden count; replaces the
       *  default `+N` badge when provided. */
      overflow?: (count: number) => React.ReactNode
    }
    
    const AvatarGroup = React.forwardRef<HTMLDivElement, AvatarGroupProps>(
      ({ className, max, overlap = true, size = 'default', total, overflow, children, ...props }, ref) => {
        const items = React.Children.toArray(children)
        const count = items.length
        const showOverflow = max != null && count > max
        const visible = showOverflow ? items.slice(0, max) : items
        const overflowCount = count - (max ?? count) + 1
    
        return (
          <div
            ref={ref}
            data-uipkge=""
            data-slot="avatar-group"
            className={cn('flex items-center', overlap ? '-space-x-2' : 'gap-1', className)}
            {...props}
          >
            {visible}
            {showOverflow ? (
              <div
                className={cn(
                  'bg-muted ring-background relative flex shrink-0 overflow-hidden rounded-full ring-2',
                  overflowSizeClasses[size],
                )}
              >
                {overflow ? (
                  overflow(overflowCount)
                ) : (
                  <span className="flex size-full items-center justify-center font-medium">+{overflowCount}</span>
                )}
              </div>
            ) : null}
          </div>
        )
      },
    )
    AvatarGroup.displayName = 'AvatarGroup'
    
    export { Avatar, AvatarImage, AvatarFallback, AvatarGroup }
  • components/ui/avatar/avatar.variants.ts 3.4 kB
    import type { VariantProps } from 'class-variance-authority'
    import { cva } from 'class-variance-authority'
    
    /**
     * Variant definitions live in their own file (rather than the package
     * `index.ts` or inline in the SFC) so `Avatar.vue` / `AvatarFallback.vue`
     * can `import { avatarVariants } from './avatar.variants'` without
     * creating a circular dependency back through the index. The circular
     * form caused intermittent `$setup.avatarVariants is not a function`
     * errors during dev SSR.
     */
    export const avatarVariants = cva('relative flex shrink-0 overflow-hidden', {
      variants: {
        size: {
          xs: 'size-4',
          sm: 'size-6',
          default: 'size-8',
          lg: 'size-12',
          xl: 'size-16',
          '2xl': 'size-20',
        },
        rounded: {
          none: 'rounded-none',
          sm: 'rounded-sm',
          default: 'rounded-full',
          md: 'rounded-md',
          lg: 'rounded-lg',
          xl: 'rounded-xl',
          '2xl': 'rounded-2xl',
          '3xl': 'rounded-3xl',
          full: 'rounded-full',
        },
        color: {
          default: '',
          primary: 'bg-primary text-primary-foreground',
          secondary: 'bg-secondary text-secondary-foreground',
          destructive: 'bg-destructive text-destructive-foreground',
          success: 'bg-[var(--success)] text-white dark:text-black',
          warning: 'bg-[var(--warning)] text-black',
          info: 'bg-[var(--info)] text-white dark:text-black',
          error: 'bg-destructive text-white dark:text-black',
          muted: 'bg-muted text-muted-foreground',
        },
        variant: {
          default: '',
          outlined: 'border-2 border-current',
          soft: 'bg-opacity-20',
        },
      },
      compoundVariants: [
        { color: 'primary', variant: 'soft', class: 'bg-primary/20 text-primary' },
        { color: 'secondary', variant: 'soft', class: 'bg-secondary/20 text-secondary-foreground' },
        { color: 'destructive', variant: 'soft', class: 'bg-destructive/20 text-destructive' },
        { color: 'success', variant: 'soft', class: 'bg-[var(--success)]/20 text-[var(--success)]' },
        { color: 'warning', variant: 'soft', class: 'bg-[var(--warning)]/20 text-[var(--warning)]' },
        { color: 'info', variant: 'soft', class: 'bg-[var(--info)]/20 text-[var(--info)]' },
        { color: 'error', variant: 'soft', class: 'bg-destructive/20 text-destructive' },
      ],
      defaultVariants: {
        size: 'default',
        rounded: 'default',
        color: 'default',
      },
    })
    
    export type AvatarVariants = VariantProps<typeof avatarVariants>
    
    export const avatarFallbackVariants = cva(
      'flex size-full items-center justify-center rounded-full bg-muted font-medium',
      {
        variants: {
          size: {
            xs: 'text-[8px]',
            sm: 'text-xs',
            default: 'text-sm',
            lg: 'text-base',
            xl: 'text-lg',
            '2xl': 'text-xl',
          },
          color: {
            default: '',
            primary: 'bg-primary text-primary-foreground',
            secondary: 'bg-secondary text-secondary-foreground',
            destructive: 'bg-destructive text-destructive-foreground',
            success: 'bg-[var(--success)] text-white dark:text-black',
            warning: 'bg-[var(--warning)] text-black',
            info: 'bg-[var(--info)] text-white dark:text-black',
            error: 'bg-destructive text-white dark:text-black',
            muted: 'bg-muted text-muted-foreground',
          },
        },
        defaultVariants: {
          size: 'default',
          color: 'default',
        },
      },
    )
    
    export type AvatarFallbackVariants = VariantProps<typeof avatarFallbackVariants>
  • components/ui/avatar/index.ts 0.4 kB
    export { Avatar, AvatarImage, AvatarFallback, AvatarGroup, type AvatarProps, type AvatarFallbackProps, type AvatarGroupProps } from './avatar'
    
    // Re-export variant API from the sibling file (kept separate to mirror the
    // Vue registry convention and avoid a component <-> index circular import).
    export {
      avatarVariants,
      avatarFallbackVariants,
      type AvatarVariants,
      type AvatarFallbackVariants,
    } from './avatar.variants'

Raw manifest: https://react.uipkge.dev/r/react/avatar.json