{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "avatar",
  "title": "Avatar",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-svelte/components/avatar/Avatar.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface AvatarProps extends HTMLAttributes<HTMLSpanElement> {\n    size?: 'xs' | 'sm' | 'default' | 'lg' | 'xl' | '2xl'\n    rounded?: 'none' | 'sm' | 'default' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'full'\n    color?: 'default' | 'primary' | 'secondary' | 'destructive' | 'success' | 'warning' | 'info' | 'error' | 'muted'\n    variant?: 'default' | 'outlined' | 'soft'\n    tile?: boolean\n    disabled?: boolean\n    loading?: boolean\n    ref?: HTMLSpanElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import type { MouseEventHandler } from 'svelte/elements'\n  import { setContext } from 'svelte'\n  import { cn } from '$lib/utils'\n  import { avatarVariants } from './avatar.variants'\n  import { AVATAR_CONTEXT_KEY, AvatarContextState } from './context.svelte'\n\n  let {\n    class: className,\n    size,\n    rounded,\n    color,\n    variant,\n    tile = false,\n    disabled = false,\n    loading = false,\n    children,\n    ref = $bindable(null),\n    onclick,\n    ...restProps\n  }: AvatarProps = $props()\n\n  // Sibling AvatarFallback shows until a sibling AvatarImage reports loaded.\n  setContext(AVATAR_CONTEXT_KEY, new AvatarContextState())\n\n  const handleClick: MouseEventHandler<HTMLSpanElement> = (event) => {\n    if (!disabled) {\n      onclick?.(event)\n    }\n  }\n\n  const rootClasses = $derived(\n    cn(\n      avatarVariants({ size, rounded, color, variant }),\n      tile ? 'rounded-none' : '',\n      disabled ? 'cursor-not-allowed opacity-50' : '',\n      loading ? 'animate-pulse' : '',\n      className,\n    ),\n  )\n</script>\n\n<span bind:this={ref} class={rootClasses} data-uipkge=\"\" data-slot=\"avatar\" onclick={handleClick} {...restProps}>\n  {@render children?.()}\n</span>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/avatar/Avatar.svelte"
    },
    {
      "path": "packages/registry-svelte/components/avatar/AvatarFallback.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface AvatarFallbackProps extends HTMLAttributes<HTMLSpanElement> {\n    size?: 'xs' | 'sm' | 'default' | 'lg' | 'xl' | '2xl'\n    color?: 'default' | 'primary' | 'secondary' | 'destructive' | 'success' | 'warning' | 'info' | 'error' | 'muted'\n    text?: string\n    ref?: HTMLSpanElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { getContext, hasContext } from 'svelte'\n  import { cn } from '$lib/utils'\n  import { avatarFallbackVariants } from './avatar.variants'\n  import { AVATAR_CONTEXT_KEY, type AvatarContextState } from './context.svelte'\n\n  let {\n    class: className,\n    size = 'default',\n    color = 'default',\n    text,\n    children,\n    ref = $bindable(null),\n    ...restProps\n  }: AvatarFallbackProps = $props()\n\n  // Show fallback until a sibling AvatarImage reports loaded (Radix composition).\n  const ctx = hasContext(AVATAR_CONTEXT_KEY) ? getContext<AvatarContextState>(AVATAR_CONTEXT_KEY) : undefined\n  const visible = $derived(!ctx || ctx.status !== 'loaded')\n\n  const rootClasses = $derived(cn(avatarFallbackVariants({ size, color }), className))\n</script>\n\n{#if visible}\n  <span bind:this={ref} class={rootClasses} data-uipkge=\"\" data-slot=\"avatar-fallback\" {...restProps}>\n    {#if text}\n      {text}\n    {:else}\n      {@render children?.()}\n    {/if}\n  </span>\n{/if}\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/avatar/AvatarFallback.svelte"
    },
    {
      "path": "packages/registry-svelte/components/avatar/AvatarGroup.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface AvatarGroupProps extends HTMLAttributes<HTMLDivElement> {\n    /**\n     * Max visible slots including the +N badge. Svelte can't slice snippet\n     * children like Vue can slice VNodes, so when overflowing render at most\n     * `max - 1` avatars and let `total` drive the badge count.\n     */\n    max?: number\n    overlap?: boolean\n    size?: 'xs' | 'sm' | 'default' | 'lg' | 'xl' | '2xl'\n    /** Override the total used for +N when only a subset of avatars is rendered. */\n    total?: number\n    overflow?: Snippet<[{ count: number }]>\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { cn } from '$lib/utils'\n\n  let {\n    class: className,\n    max,\n    overlap = true,\n    size = 'default',\n    total,\n    children,\n    overflow,\n    ref = $bindable(null),\n    ...restProps\n  }: AvatarGroupProps = $props()\n\n  // When overflowing, reserve one slot for the +N chip so max includes the badge.\n  const isOverflowing = $derived(max != null && total != null && total > max)\n  const overflowCount = $derived(isOverflowing ? total! - (max! - 1) : 0)\n\n  const overflowSizeClass = $derived.by(() => {\n    switch (size) {\n      case 'xs':\n        return 'size-4 text-xs'\n      case 'sm':\n        return 'size-6 text-xs'\n      case 'lg':\n        return 'size-12 text-base'\n      case 'xl':\n        return 'size-16 text-lg'\n      case '2xl':\n        return 'size-20 text-xl'\n      default:\n        return 'size-8 text-sm'\n    }\n  })\n</script>\n\n<div\n  bind:this={ref}\n  class={cn('flex items-center', overlap ? '-space-x-2' : 'gap-1', className)}\n  data-uipkge=\"\"\n  data-slot=\"avatar-group\"\n  {...restProps}\n>\n  {@render children?.()}\n  {#if isOverflowing}\n    <div class={cn('bg-muted ring-background relative flex shrink-0 overflow-hidden rounded-full ring-2', overflowSizeClass)}>\n      {#if overflow}\n        {@render overflow({ count: overflowCount })}\n      {:else}\n        <span class=\"flex size-full items-center justify-center font-medium\">+{overflowCount}</span>\n      {/if}\n    </div>\n  {/if}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/avatar/AvatarGroup.svelte"
    },
    {
      "path": "packages/registry-svelte/components/avatar/AvatarImage.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLImgAttributes } from 'svelte/elements'\n\n  export interface AvatarImageProps extends HTMLImgAttributes {\n    ref?: HTMLImageElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import type { EventHandler } from 'svelte/elements'\n  import { getContext, hasContext, onMount } from 'svelte'\n  import { cn } from '$lib/utils'\n  import { AVATAR_CONTEXT_KEY, type AvatarContextState } from './context.svelte'\n\n  let {\n    class: className,\n    src,\n    alt,\n    // Radix's React AvatarImage has no lazy default; lazily-loaded avatars below\n    // the fold would sit on their fallback initials and diverge from the React\n    // mirror, so eager is the parity-correct default.\n    loading = 'eager',\n    ref = $bindable(null),\n    onload,\n    onerror,\n    ...restProps\n  }: AvatarImageProps = $props()\n\n  const ctx = hasContext(AVATAR_CONTEXT_KEY) ? getContext<AvatarContextState>(AVATAR_CONTEXT_KEY) : undefined\n  const status = $derived(ctx?.status ?? 'idle')\n\n  // Runs before mount (like Vue's immediate watcher): mark loading as soon as\n  // a src exists so the fallback shows while the image fetches.\n  $effect.pre(() => {\n    ctx?.setStatus(src ? 'loading' : 'idle')\n  })\n\n  // An image that finished before this component attached its listener never\n  // fires `load`/`error`, so reconcile from the element state on mount: a\n  // complete image with pixels is loaded; a complete image without them failed.\n  // (`bind:this` below has already assigned `ref` when onMount runs.)\n  onMount(() => {\n    const el = ref\n    if (!el?.complete) return\n    ctx?.setStatus(el.naturalWidth > 0 ? 'loaded' : 'error')\n  })\n\n  const handleError: EventHandler<Event, Element> = (event) => {\n    ctx?.setStatus('error')\n    onerror?.(event)\n  }\n\n  const handleLoad: EventHandler<Event, Element> = (event) => {\n    ctx?.setStatus('loaded')\n    onload?.(event)\n  }\n</script>\n\n{#if src && status !== 'error'}\n  <img\n    bind:this={ref}\n    {src}\n    {alt}\n    {loading}\n    class={cn('aspect-square size-full object-cover', className)}\n    data-uipkge=\"\"\n    data-slot=\"avatar-image\"\n    onload={handleLoad}\n    onerror={handleError}\n    {...restProps}\n  />\n{/if}\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/avatar/AvatarImage.svelte"
    },
    {
      "path": "packages/registry-svelte/components/avatar/avatar.variants.ts",
      "content": "import type { VariantProps } from 'class-variance-authority'\nimport { cva } from 'class-variance-authority'\n\n/**\n * Variant definitions live in their own file (rather than the package\n * `index.ts` or inline in the SFC) so `Avatar.vue` / `AvatarFallback.vue`\n * can `import { avatarVariants } from './avatar.variants'` without\n * creating a circular dependency back through the index. The circular\n * form caused intermittent `$setup.avatarVariants is not a function`\n * errors during dev SSR.\n */\nexport const avatarVariants = cva('relative flex shrink-0 overflow-hidden', {\n  variants: {\n    size: {\n      xs: 'size-4',\n      sm: 'size-6',\n      default: 'size-8',\n      lg: 'size-12',\n      xl: 'size-16',\n      '2xl': 'size-20',\n    },\n    rounded: {\n      none: 'rounded-none',\n      sm: 'rounded-sm',\n      default: 'rounded-full',\n      md: 'rounded-md',\n      lg: 'rounded-lg',\n      xl: 'rounded-xl',\n      '2xl': 'rounded-2xl',\n      '3xl': 'rounded-3xl',\n      full: 'rounded-full',\n    },\n    color: {\n      default: '',\n      primary: 'bg-primary text-primary-foreground',\n      secondary: 'bg-secondary text-secondary-foreground',\n      destructive: 'bg-destructive text-destructive-foreground',\n      success: 'bg-success text-white dark:text-black',\n      warning: 'bg-warning text-black',\n      info: 'bg-info text-white dark:text-black',\n      error: 'bg-destructive text-white dark:text-black',\n      muted: 'bg-muted text-muted-foreground',\n    },\n    variant: {\n      default: '',\n      outlined: 'border-2 border-current',\n      soft: 'bg-opacity-20',\n    },\n  },\n  compoundVariants: [\n    { color: 'primary', variant: 'soft', class: 'bg-primary/20 text-primary' },\n    { color: 'secondary', variant: 'soft', class: 'bg-secondary/20 text-secondary-foreground' },\n    { color: 'destructive', variant: 'soft', class: 'bg-destructive/20 text-destructive' },\n    { color: 'success', variant: 'soft', class: 'bg-success/20 text-success' },\n    { color: 'warning', variant: 'soft', class: 'bg-warning/20 text-warning' },\n    { color: 'info', variant: 'soft', class: 'bg-info/20 text-info' },\n    { color: 'error', variant: 'soft', class: 'bg-destructive/20 text-destructive' },\n  ],\n  defaultVariants: {\n    size: 'default',\n    rounded: 'default',\n    color: 'default',\n  },\n})\n\nexport type AvatarVariants = VariantProps<typeof avatarVariants>\n\nexport const avatarFallbackVariants = cva(\n  'flex size-full items-center justify-center rounded-full bg-muted font-medium',\n  {\n    variants: {\n      size: {\n        xs: 'text-xs',\n        sm: 'text-xs',\n        default: 'text-sm',\n        lg: 'text-base',\n        xl: 'text-lg',\n        '2xl': 'text-xl',\n      },\n      color: {\n        default: '',\n        primary: 'bg-primary text-primary-foreground',\n        secondary: 'bg-secondary text-secondary-foreground',\n        destructive: 'bg-destructive text-destructive-foreground',\n        success: 'bg-success text-white dark:text-black',\n        warning: 'bg-warning text-black',\n        info: 'bg-info text-white dark:text-black',\n        error: 'bg-destructive text-white dark:text-black',\n        muted: 'bg-muted text-muted-foreground',\n      },\n    },\n    defaultVariants: {\n      size: 'default',\n      color: 'default',\n    },\n  },\n)\n\nexport type AvatarFallbackVariants = VariantProps<typeof avatarFallbackVariants>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/avatar/avatar.variants.ts"
    },
    {
      "path": "packages/registry-svelte/components/avatar/context.svelte.ts",
      "content": "export type AvatarImageStatus = 'idle' | 'loading' | 'loaded' | 'error'\n\n/**\n * Shared mutable state between `Avatar` and its `AvatarImage` /\n * `AvatarFallback` children. Created in `Avatar.svelte` and shared via\n * `setContext` / `getContext` (Svelte counterpart of Vue's provide/inject).\n */\nexport class AvatarContextState {\n  status = $state<AvatarImageStatus>('idle')\n\n  setStatus(status: AvatarImageStatus) {\n    this.status = status\n  }\n}\n\nexport const AVATAR_CONTEXT_KEY = Symbol('uipkge-avatar')\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/avatar/context.svelte.ts"
    },
    {
      "path": "packages/registry-svelte/components/avatar/index.ts",
      "content": "export { default as Avatar, type AvatarProps } from './Avatar.svelte'\nexport { default as AvatarFallback, type AvatarFallbackProps } from './AvatarFallback.svelte'\nexport { default as AvatarImage, type AvatarImageProps } from './AvatarImage.svelte'\nexport { default as AvatarGroup, type AvatarGroupProps } from './AvatarGroup.svelte'\n\n// Re-export variant API from the sibling file (kept separate to avoid the\n// Avatar.svelte <-> index.ts circular import that broke dev SSR).\nexport {\n  avatarVariants,\n  avatarFallbackVariants,\n  type AvatarVariants,\n  type AvatarFallbackVariants,\n} from './avatar.variants'\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/avatar/index.ts"
    }
  ],
  "dependencies": [
    "class-variance-authority"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "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.",
  "categories": [
    "data-display"
  ]
}