
3D Extruded Buildings & Urban Footprints
Real-world 3D building extrusions with dynamic sunlight shadows, terrain DEM elevations, and pitch/bearing camera controls.
avataruiRound 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 ->$pnpm dlx shadcn@latest add https://uipkge.dev/r/react/avatar.json$npx shadcn@latest add https://uipkge.dev/r/react/avatar.json$yarn dlx shadcn@latest add https://uipkge.dev/r/react/avatar.json$bunx shadcn@latest add https://uipkge.dev/r/react/avatar.jsonnpx shadcn@latest add @uipkge-react/avatarInstalls to:components/ui/avatar/| 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 |
'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 ? 'cursor-not-allowed opacity-50' : '',
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-xs',
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 = total ?? items.length
// When overflowing, reserve one slot for the +N chip so max includes the badge.
const showOverflow = max != null && count > max
const visibleLimit = showOverflow ? Math.max(max - 1, 0) : items.length
const visible = items.slice(0, visibleLimit)
const overflowCount = showOverflow ? count - visibleLimit : 0
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 }
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-success text-white dark:text-black',
warning: 'bg-warning text-black',
info: 'bg-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-success/20 text-success' },
{ color: 'warning', variant: 'soft', class: 'bg-warning/20 text-warning' },
{ color: 'info', variant: 'soft', class: 'bg-info/20 text-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-xs',
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-success text-white dark:text-black',
warning: 'bg-warning text-black',
info: 'bg-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>
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://uipkge.dev/r/react/avatar.json