{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "avatar",
  "type": "registry:ui",
  "title": "Avatar",
  "description": "Round image with text/icon fallback. Native <img> + inline onerror handler reveals fallback on 404. Group + count subcomponents.",
  "files": [
    {
      "path": "registry/ui/avatar.tsx",
      "type": "registry:ui",
      "target": "components/ui/avatar.tsx",
      "content": "/** @jsxImportSource hono/jsx */\nimport type { PropsWithChildren } from \"hono/jsx\"\nimport { cn, type ClassValue } from \"@/registry/lib/cn\"\n\n// Avatar — shadcn-htmx, htmx v4 + Tailwind v4.\n//\n// Source of truth (variants):\n//   repos/shadcn-ui/apps/v4/registry/new-york-v4/ui/avatar.tsx\n//\n// shadcn upstream uses Radix Avatar which manages a load/error state\n// machine (show fallback while image fetches, after fetch fails, etc.).\n// For SSR we render both layers — the fallback as a sibling, then the\n// <img> on top via absolute positioning. If the image loads, it covers\n// the fallback; if it errors, an inline onerror handler hides it,\n// revealing the fallback. Zero JS framework needed.\n//\n// MDN <img>: repos/mdn/files/en-us/web/html/reference/elements/img/index.md\n//\n// Accessibility:\n//   - The Avatar root carries the accessible name via the <img>'s alt\n//     when src is provided; when not, pass `ariaLabel` to the root so AT\n//     announces \"MK avatar\" or similar.\n//   - For purely decorative avatars (next to a name that's already in the\n//     DOM), pass alt=\"\" so AT skips it.\n\nexport type AvatarSize = \"sm\" | \"default\" | \"lg\"\n\nconst rootSize: Record<AvatarSize, string> = {\n  sm: \"size-6 [&_[data-slot=avatar-fallback]]:text-xs\",\n  default: \"size-8\",\n  lg: \"size-10\",\n}\n\ntype AvatarProps = PropsWithChildren<{\n  size?: AvatarSize\n  // The image URL. When undefined, only the fallback renders.\n  src?: string\n  alt?: string\n  // Fallback text — typically initials. Ignored when children are provided.\n  fallback?: string\n  // Accessible name for the avatar group when there's no img (no alt).\n  ariaLabel?: string\n  // Native <img loading>. Avatars are often off-screen in long lists, so\n  // loading=\"lazy\" defers fetching until near the viewport.\n  // MDN <img>: .../elements/img/index.md \"loading\".\n  loading?: \"eager\" | \"lazy\"\n  // Native <img referrerpolicy>. Avatar src is often a third-party host\n  // (Gravatar/OAuth/CDN); e.g. \"no-referrer\" avoids leaking the page URL.\n  // MDN <img>: .../elements/img/index.md \"referrerpolicy\".\n  referrerpolicy?:\n    | \"no-referrer\"\n    | \"no-referrer-when-downgrade\"\n    | \"origin\"\n    | \"origin-when-cross-origin\"\n    | \"same-origin\"\n    | \"strict-origin\"\n    | \"strict-origin-when-cross-origin\"\n    | \"unsafe-url\"\n  // Native <img srcset>/<img sizes>. Primarily for the retina \"2x\" pixel\n  // density descriptor on small fixed-size avatars; src is the 1x fallback.\n  // MDN <img>: .../elements/img/index.md \"srcset\".\n  srcset?: string\n  sizes?: string\n  class?: ClassValue\n}>\n\nexport function Avatar(props: AvatarProps) {\n  const { size = \"default\", src, alt, fallback, ariaLabel, loading, referrerpolicy, srcset, sizes, class: className, children } = props\n  return (\n    <span\n      data-slot=\"avatar\"\n      data-size={size}\n      role={!src && (fallback || children) ? \"img\" : undefined}\n      aria-label={!src ? ariaLabel ?? fallback : undefined}\n      class={cn(\n        \"group/avatar relative inline-flex shrink-0 overflow-hidden rounded-full select-none\",\n        rootSize[size],\n        className,\n      )}\n    >\n      {/* Fallback layer — visible by default; covered by the <img> if it\n          loads successfully. Children win over `fallback` text. */}\n      <span\n        data-slot=\"avatar-fallback\"\n        class={cn(\n          \"flex size-full items-center justify-center rounded-full bg-muted text-sm font-medium text-muted-foreground\",\n        )}\n      >\n        {children ?? fallback}\n      </span>\n      {src && (\n        <img\n          src={src}\n          srcset={srcset}\n          sizes={sizes}\n          alt={alt ?? \"\"}\n          loading={loading}\n          referrerpolicy={referrerpolicy}\n          data-slot=\"avatar-image\"\n          // When the image fails to load (404, network error), hide it and\n          // let the fallback layer underneath show through.\n          onerror=\"this.style.display='none'\"\n          class=\"absolute inset-0 aspect-square size-full object-cover\"\n        />\n      )}\n    </span>\n  )\n}\n\n// Optional status badge — pin a coloured dot or icon to the corner.\ntype AvatarBadgeProps = PropsWithChildren<{ class?: ClassValue }>\nexport function AvatarBadge(props: AvatarBadgeProps) {\n  return (\n    <span\n      data-slot=\"avatar-badge\"\n      class={cn(\n        \"absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground ring-2 ring-background select-none\",\n        \"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden\",\n        \"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2\",\n        \"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2\",\n        props.class,\n      )}\n    >\n      {props.children}\n    </span>\n  )\n}\n\n// Stack avatars with the typical -space-x overlap.\ntype AvatarGroupProps = PropsWithChildren<{ class?: ClassValue }>\nexport function AvatarGroup(props: AvatarGroupProps) {\n  return (\n    <div\n      data-slot=\"avatar-group\"\n      class={cn(\n        \"group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background\",\n        props.class,\n      )}\n    >\n      {props.children}\n    </div>\n  )\n}\n\n// \"+N more\" counter that matches AvatarGroup sizing.\ntype AvatarGroupCountProps = PropsWithChildren<{ class?: ClassValue }>\nexport function AvatarGroupCount(props: AvatarGroupCountProps) {\n  return (\n    <div\n      data-slot=\"avatar-group-count\"\n      class={cn(\n        \"relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background\",\n        \"group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6\",\n        \"[&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3\",\n        props.class,\n      )}\n    >\n      {props.children}\n    </div>\n  )\n}\n"
    },
    {
      "path": "registry/jinja2/avatar.html",
      "type": "registry:file",
      "target": "templates/components/avatar.html",
      "content": "{# Avatar macro — shadcn-htmx, htmx v4 + Tailwind v4.\n   Mirrors registry/ui/avatar.tsx. Native <img> + fallback layer.\n\n   Usage:\n     {% from \"components/avatar.html\" import avatar %}\n     {{ avatar(src=\"/users/mk.jpg\", alt=\"Mehmet K\", fallback=\"MK\") }}\n     {{ avatar(fallback=\"MK\", aria_label=\"Mehmet K\") }}              {# no image #} #}\n\n{% macro avatar(src=none, alt=none, fallback=none, size=\"default\", aria_label=none, loading=none, referrerpolicy=none, srcset=none, sizes=none, extra_class=\"\") %}\n{%- set sizes = {\n    \"sm\": \"size-6 [&_[data-slot=avatar-fallback]]:text-xs\",\n    \"default\": \"size-8\",\n    \"lg\": \"size-10\"\n} -%}\n<span data-slot=\"avatar\" data-size=\"{{ size }}\"\n      {%- if not src and (fallback or aria_label) %} role=\"img\" aria-label=\"{{ aria_label or fallback }}\"{% endif %}\n      class=\"group/avatar relative inline-flex shrink-0 overflow-hidden rounded-full select-none {{ sizes[size] }} {{ extra_class }}\">\n  <span data-slot=\"avatar-fallback\"\n    class=\"flex size-full items-center justify-center rounded-full bg-muted text-sm font-medium text-muted-foreground\">{{ fallback or \"\" }}</span>\n  {% if src %}\n  {# MDN <img>: loading (lazy off-screen), referrerpolicy (3rd-party hosts), srcset/sizes (retina 2x). #}\n  <img src=\"{{ src }}\" alt=\"{{ alt or '' }}\" data-slot=\"avatar-image\"\n       {%- if srcset %} srcset=\"{{ srcset }}\"{% endif %}\n       {%- if sizes %} sizes=\"{{ sizes }}\"{% endif %}\n       {%- if loading %} loading=\"{{ loading }}\"{% endif %}\n       {%- if referrerpolicy %} referrerpolicy=\"{{ referrerpolicy }}\"{% endif %}\n       onerror=\"this.style.display='none'\"\n       class=\"absolute inset-0 aspect-square size-full object-cover\">\n  {% endif %}\n</span>\n{% endmacro %}\n"
    },
    {
      "path": "registry/go-templates/avatar.tmpl",
      "type": "registry:file",
      "target": "components/avatar.tmpl",
      "content": "{{/*\n  Avatar template — shadcn-htmx, htmx v4 + Tailwind v4.\n\n      type AvatarArgs struct {\n          Src, Alt, Fallback, Size, AriaLabel string\n          // MDN <img>: Loading (lazy off-screen), ReferrerPolicy (3rd-party\n          // hosts), Srcset/Sizes (retina 2x). All optional.\n          Loading, ReferrerPolicy, Srcset, Sizes string\n      }\n*/}}\n\n{{define \"avatar\"}}\n{{- $size := or .Size \"default\" -}}\n{{- $sizes := dict\n    \"sm\" \"size-6 [&_[data-slot=avatar-fallback]]:text-xs\"\n    \"default\" \"size-8\"\n    \"lg\" \"size-10\" -}}\n<span data-slot=\"avatar\" data-size=\"{{$size}}\"\n      {{if and (not .Src) (or .Fallback .AriaLabel)}}role=\"img\" aria-label=\"{{or .AriaLabel .Fallback}}\"{{end}}\n      class=\"group/avatar relative inline-flex shrink-0 overflow-hidden rounded-full select-none {{index $sizes $size}}\">\n  <span data-slot=\"avatar-fallback\" class=\"flex size-full items-center justify-center rounded-full bg-muted text-sm font-medium text-muted-foreground\">{{.Fallback}}</span>\n  {{if .Src}}\n  <img src=\"{{.Src}}\" alt=\"{{.Alt}}\" data-slot=\"avatar-image\"\n       {{if .Srcset}}srcset=\"{{.Srcset}}\" {{end}}{{if .Sizes}}sizes=\"{{.Sizes}}\" {{end}}{{if .Loading}}loading=\"{{.Loading}}\" {{end}}{{if .ReferrerPolicy}}referrerpolicy=\"{{.ReferrerPolicy}}\" {{end}}\n       onerror=\"this.style.display='none'\"\n       class=\"absolute inset-0 aspect-square size-full object-cover\">\n  {{end}}\n</span>\n{{end}}\n"
    },
    {
      "path": "registry/phoenix/avatar.ex",
      "type": "registry:file",
      "target": "lib/my_app_web/components/avatar.ex",
      "content": "defmodule ShadcnHtmx.Components.Avatar do\n  @moduledoc \"\"\"\n  Avatar — shadcn-htmx, htmx v4 + Tailwind v4 for Phoenix.\n\n  Native `<img>` on top of a fallback layer. If the image errors, an\n  inline onerror handler hides it so the fallback shows through.\n\n  ## Examples\n\n      <.avatar src={~p\"/users/mk.jpg\"} alt=\"Mehmet K\" fallback=\"MK\" />\n      <.avatar fallback=\"MK\" aria-label=\"Mehmet K\" />\n  \"\"\"\n\n  use Phoenix.Component\n\n  @sizes %{\n    \"sm\" => \"size-6 [&_[data-slot=avatar-fallback]]:text-xs\",\n    \"default\" => \"size-8\",\n    \"lg\" => \"size-10\"\n  }\n\n  attr :src, :string, default: nil\n  attr :alt, :string, default: nil\n  attr :fallback, :string, default: nil\n  attr :size, :string, default: \"default\", values: ~w(sm default lg)\n  attr :\"aria-label\", :string, default: nil\n  # MDN <img>: loading (lazy off-screen), referrerpolicy (3rd-party hosts),\n  # srcset/sizes (retina 2x). All optional, forwarded to the inner <img>.\n  attr :loading, :string, default: nil\n  attr :referrerpolicy, :string, default: nil\n  attr :srcset, :string, default: nil\n  attr :sizes, :string, default: nil\n  attr :class, :string, default: nil\n\n  def avatar(assigns) do\n    assigns =\n      assigns\n      |> assign(:size_class, Map.fetch!(@sizes, assigns.size))\n      |> assign(:aria_label, assigns[:\"aria-label\"])\n\n    ~H\"\"\"\n    <span\n      data-slot=\"avatar\"\n      data-size={@size}\n      role={if !@src and (@fallback || @aria_label), do: \"img\"}\n      aria-label={if !@src, do: @aria_label || @fallback}\n      class={[\n        \"group/avatar relative inline-flex shrink-0 overflow-hidden rounded-full select-none\",\n        @size_class,\n        @class\n      ]}\n    >\n      <span\n        data-slot=\"avatar-fallback\"\n        class=\"flex size-full items-center justify-center rounded-full bg-muted text-sm font-medium text-muted-foreground\"\n      >\n        {@fallback}\n      </span>\n      <img\n        :if={@src}\n        src={@src}\n        srcset={@srcset}\n        sizes={@sizes}\n        alt={@alt || \"\"}\n        loading={@loading}\n        referrerpolicy={@referrerpolicy}\n        data-slot=\"avatar-image\"\n        onerror=\"this.style.display='none'\"\n        class=\"absolute inset-0 aspect-square size-full object-cover\"\n      />\n    </span>\n    \"\"\"\n  end\nend\n"
    },
    {
      "path": "registry/html/avatar.html",
      "type": "registry:file",
      "target": "snippets/avatar.html",
      "content": "<!--\n  shadcn-htmx — raw HTML avatar snippet.\n\n  Mirrors registry/ui/avatar.tsx. Fallback layer + <img> on top; onerror\n  hides a broken image so the fallback shows through.\n-->\n\n<!-- With image. Optional <img> attrs (MDN): loading=\"lazy\" defers off-screen\n     fetches; referrerpolicy avoids leaking the page URL to 3rd-party hosts;\n     srcset with an \"x\" descriptor serves a sharper 2x source on retina. -->\n<span data-slot=\"avatar\" data-size=\"default\"\n  class=\"group/avatar relative inline-flex shrink-0 overflow-hidden rounded-full select-none size-8\">\n  <span data-slot=\"avatar-fallback\"\n    class=\"flex size-full items-center justify-center rounded-full bg-muted text-sm font-medium text-muted-foreground\">MK</span>\n  <img src=\"/users/mk.jpg\" srcset=\"/users/mk@2x.jpg 2x\" alt=\"Mehmet K\" data-slot=\"avatar-image\"\n       loading=\"lazy\" referrerpolicy=\"no-referrer\"\n       onerror=\"this.style.display='none'\"\n       class=\"absolute inset-0 aspect-square size-full object-cover\">\n</span>\n\n<!-- Fallback-only (no src) -->\n<span data-slot=\"avatar\" data-size=\"default\" role=\"img\" aria-label=\"Mehmet K\"\n  class=\"group/avatar relative inline-flex shrink-0 overflow-hidden rounded-full select-none size-8\">\n  <span data-slot=\"avatar-fallback\"\n    class=\"flex size-full items-center justify-center rounded-full bg-muted text-sm font-medium text-muted-foreground\">MK</span>\n</span>\n\n<!-- Group (overlapping stack) -->\n<div data-slot=\"avatar-group\" class=\"group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background\">\n  <span data-slot=\"avatar\" data-size=\"default\" role=\"img\" aria-label=\"A. B\" class=\"group/avatar relative inline-flex size-8 shrink-0 overflow-hidden rounded-full select-none\">\n    <span data-slot=\"avatar-fallback\" class=\"flex size-full items-center justify-center rounded-full bg-muted text-sm font-medium text-muted-foreground\">AB</span>\n  </span>\n  <span data-slot=\"avatar\" data-size=\"default\" role=\"img\" aria-label=\"C. D\" class=\"group/avatar relative inline-flex size-8 shrink-0 overflow-hidden rounded-full select-none\">\n    <span data-slot=\"avatar-fallback\" class=\"flex size-full items-center justify-center rounded-full bg-muted text-sm font-medium text-muted-foreground\">CD</span>\n  </span>\n  <div data-slot=\"avatar-group-count\" class=\"relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background\">+3</div>\n</div>\n"
    }
  ]
}
