shshadcn-htmx

Components

Avatar

A round image with text/icon fallback. SSR-friendly: we render both layers and let the platform pick the visible one — image on top via onerror="this.style.display='none'" reveals the fallback when the URL 404s.

Installation

1. Install via the shadcn CLI

npx shadcn@latest add http://localhost/r/avatar.json

2. Use it

components/ui/avatar.tsx
import { Avatar } from "@/components/ui/avatar"

<Avatar src="/users/mk.jpg" alt="Mehmet K" fallback="MK" />
<Avatar fallback="MK" ariaLabel="Mehmet K" />
Or copy the source manually
components/ui/avatar.tsx
/** @jsxImportSource hono/jsx */
import type { PropsWithChildren } from "hono/jsx"
import { cn, type ClassValue } from "@/registry/lib/cn"

// Avatar — shadcn-htmx, htmx v4 + Tailwind v4.
//
// Source of truth (variants):
//   repos/shadcn-ui/apps/v4/registry/new-york-v4/ui/avatar.tsx
//
// shadcn upstream uses Radix Avatar which manages a load/error state
// machine (show fallback while image fetches, after fetch fails, etc.).
// For SSR we render both layers — the fallback as a sibling, then the
// <img> on top via absolute positioning. If the image loads, it covers
// the fallback; if it errors, an inline onerror handler hides it,
// revealing the fallback. Zero JS framework needed.
//
// MDN <img>: repos/mdn/files/en-us/web/html/reference/elements/img/index.md
//
// Accessibility:
//   - The Avatar root carries the accessible name via the <img>'s alt
//     when src is provided; when not, pass `ariaLabel` to the root so AT
//     announces "MK avatar" or similar.
//   - For purely decorative avatars (next to a name that's already in the
//     DOM), pass alt="" so AT skips it.

export type AvatarSize = "sm" | "default" | "lg"

const rootSize: Record<AvatarSize, string> = {
  sm: "size-6 [&_[data-slot=avatar-fallback]]:text-xs",
  default: "size-8",
  lg: "size-10",
}

type AvatarProps = PropsWithChildren<{
  size?: AvatarSize
  // The image URL. When undefined, only the fallback renders.
  src?: string
  alt?: string
  // Fallback text — typically initials. Ignored when children are provided.
  fallback?: string
  // Accessible name for the avatar group when there's no img (no alt).
  ariaLabel?: string
  // Native <img loading>. Avatars are often off-screen in long lists, so
  // loading="lazy" defers fetching until near the viewport.
  // MDN <img>: .../elements/img/index.md "loading".
  loading?: "eager" | "lazy"
  // Native <img referrerpolicy>. Avatar src is often a third-party host
  // (Gravatar/OAuth/CDN); e.g. "no-referrer" avoids leaking the page URL.
  // MDN <img>: .../elements/img/index.md "referrerpolicy".
  referrerpolicy?:
    | "no-referrer"
    | "no-referrer-when-downgrade"
    | "origin"
    | "origin-when-cross-origin"
    | "same-origin"
    | "strict-origin"
    | "strict-origin-when-cross-origin"
    | "unsafe-url"
  // Native <img srcset>/<img sizes>. Primarily for the retina "2x" pixel
  // density descriptor on small fixed-size avatars; src is the 1x fallback.
  // MDN <img>: .../elements/img/index.md "srcset".
  srcset?: string
  sizes?: string
  class?: ClassValue
}>

export function Avatar(props: AvatarProps) {
  const { size = "default", src, alt, fallback, ariaLabel, loading, referrerpolicy, srcset, sizes, class: className, children } = props
  return (
    <span
      data-slot="avatar"
      data-size={size}
      role={!src && (fallback || children) ? "img" : undefined}
      aria-label={!src ? ariaLabel ?? fallback : undefined}
      class={cn(
        "group/avatar relative inline-flex shrink-0 overflow-hidden rounded-full select-none",
        rootSize[size],
        className,
      )}
    >
      {/* Fallback layer — visible by default; covered by the <img> if it
          loads successfully. Children win over `fallback` text. */}
      <span
        data-slot="avatar-fallback"
        class={cn(
          "flex size-full items-center justify-center rounded-full bg-muted text-sm font-medium text-muted-foreground",
        )}
      >
        {children ?? fallback}
      </span>
      {src && (
        <img
          src={src}
          srcset={srcset}
          sizes={sizes}
          alt={alt ?? ""}
          loading={loading}
          referrerpolicy={referrerpolicy}
          data-slot="avatar-image"
          // When the image fails to load (404, network error), hide it and
          // let the fallback layer underneath show through.
          onerror="this.style.display='none'"
          class="absolute inset-0 aspect-square size-full object-cover"
        />
      )}
    </span>
  )
}

// Optional status badge — pin a coloured dot or icon to the corner.
type AvatarBadgeProps = PropsWithChildren<{ class?: ClassValue }>
export function AvatarBadge(props: AvatarBadgeProps) {
  return (
    <span
      data-slot="avatar-badge"
      class={cn(
        "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",
        "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",
        props.class,
      )}
    >
      {props.children}
    </span>
  )
}

// Stack avatars with the typical -space-x overlap.
type AvatarGroupProps = PropsWithChildren<{ class?: ClassValue }>
export function AvatarGroup(props: AvatarGroupProps) {
  return (
    <div
      data-slot="avatar-group"
      class={cn(
        "group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background",
        props.class,
      )}
    >
      {props.children}
    </div>
  )
}

// "+N more" counter that matches AvatarGroup sizing.
type AvatarGroupCountProps = PropsWithChildren<{ class?: ClassValue }>
export function AvatarGroupCount(props: AvatarGroupCountProps) {
  return (
    <div
      data-slot="avatar-group-count"
      class={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",
        props.class,
      )}
    >
      {props.children}
    </div>
  )
}

1. Save the file

Copy avatar.html into templates/components/.

2. Use it

templates/components/avatar.html
{% from "components/avatar.html" import avatar %}

{{ avatar(src="/users/mk.jpg", alt="Mehmet K", fallback="MK") }}
{{ avatar(fallback="MK", aria_label="Mehmet K") }}
View source
templates/components/avatar.html
{# Avatar macro — shadcn-htmx, htmx v4 + Tailwind v4.
   Mirrors registry/ui/avatar.tsx. Native <img> + fallback layer.

   Usage:
     {% from "components/avatar.html" import avatar %}
     {{ avatar(src="/users/mk.jpg", alt="Mehmet K", fallback="MK") }}
     {{ avatar(fallback="MK", aria_label="Mehmet K") }}              {# no image #} #}

{% macro avatar(src=none, alt=none, fallback=none, size="default", aria_label=none, loading=none, referrerpolicy=none, srcset=none, sizes=none, extra_class="") %}
{%- set sizes = {
    "sm": "size-6 [&_[data-slot=avatar-fallback]]:text-xs",
    "default": "size-8",
    "lg": "size-10"
} -%}
<span data-slot="avatar" data-size="{{ size }}"
      {%- if not src and (fallback or aria_label) %} role="img" aria-label="{{ aria_label or fallback }}"{% endif %}
      class="group/avatar relative inline-flex shrink-0 overflow-hidden rounded-full select-none {{ sizes[size] }} {{ extra_class }}">
  <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 or "" }}</span>
  {% if src %}
  {# MDN <img>: loading (lazy off-screen), referrerpolicy (3rd-party hosts), srcset/sizes (retina 2x). #}
  <img src="{{ src }}" alt="{{ alt or '' }}" data-slot="avatar-image"
       {%- if srcset %} srcset="{{ srcset }}"{% endif %}
       {%- if sizes %} sizes="{{ sizes }}"{% endif %}
       {%- if loading %} loading="{{ loading }}"{% endif %}
       {%- if referrerpolicy %} referrerpolicy="{{ referrerpolicy }}"{% endif %}
       onerror="this.style.display='none'"
       class="absolute inset-0 aspect-square size-full object-cover">
  {% endif %}
</span>
{% endmacro %}

1. Save the file

Add avatar.tmpl alongside button.tmpl.

2. Use it

templates/components/avatar.tmpl
{{template "avatar" (dict "Src" "/users/mk.jpg" "Alt" "Mehmet K" "Fallback" "MK")}}
View source
templates/components/avatar.tmpl
{{/*
  Avatar template — shadcn-htmx, htmx v4 + Tailwind v4.

      type AvatarArgs struct {
          Src, Alt, Fallback, Size, AriaLabel string
          // MDN <img>: Loading (lazy off-screen), ReferrerPolicy (3rd-party
          // hosts), Srcset/Sizes (retina 2x). All optional.
          Loading, ReferrerPolicy, Srcset, Sizes string
      }
*/}}

{{define "avatar"}}
{{- $size := or .Size "default" -}}
{{- $sizes := dict
    "sm" "size-6 [&_[data-slot=avatar-fallback]]:text-xs"
    "default" "size-8"
    "lg" "size-10" -}}
<span data-slot="avatar" data-size="{{$size}}"
      {{if and (not .Src) (or .Fallback .AriaLabel)}}role="img" aria-label="{{or .AriaLabel .Fallback}}"{{end}}
      class="group/avatar relative inline-flex shrink-0 overflow-hidden rounded-full select-none {{index $sizes $size}}">
  <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>
  {{if .Src}}
  <img src="{{.Src}}" alt="{{.Alt}}" data-slot="avatar-image"
       {{if .Srcset}}srcset="{{.Srcset}}" {{end}}{{if .Sizes}}sizes="{{.Sizes}}" {{end}}{{if .Loading}}loading="{{.Loading}}" {{end}}{{if .ReferrerPolicy}}referrerpolicy="{{.ReferrerPolicy}}" {{end}}
       onerror="this.style.display='none'"
       class="absolute inset-0 aspect-square size-full object-cover">
  {{end}}
</span>
{{end}}

1. Save the file

Drop avatar.ex into lib/my_app_web/components/.

2. Use it

lib/my_app_web/components/avatar.ex
<.avatar src={~p"/users/mk.jpg"} alt="Mehmet K" fallback="MK" />
View source
lib/my_app_web/components/avatar.ex
defmodule ShadcnHtmx.Components.Avatar do
  @moduledoc """
  Avatar — shadcn-htmx, htmx v4 + Tailwind v4 for Phoenix.

  Native `<img>` on top of a fallback layer. If the image errors, an
  inline onerror handler hides it so the fallback shows through.

  ## Examples

      <.avatar src={~p"/users/mk.jpg"} alt="Mehmet K" fallback="MK" />
      <.avatar fallback="MK" aria-label="Mehmet K" />
  """

  use Phoenix.Component

  @sizes %{
    "sm" => "size-6 [&_[data-slot=avatar-fallback]]:text-xs",
    "default" => "size-8",
    "lg" => "size-10"
  }

  attr :src, :string, default: nil
  attr :alt, :string, default: nil
  attr :fallback, :string, default: nil
  attr :size, :string, default: "default", values: ~w(sm default lg)
  attr :"aria-label", :string, default: nil
  # MDN <img>: loading (lazy off-screen), referrerpolicy (3rd-party hosts),
  # srcset/sizes (retina 2x). All optional, forwarded to the inner <img>.
  attr :loading, :string, default: nil
  attr :referrerpolicy, :string, default: nil
  attr :srcset, :string, default: nil
  attr :sizes, :string, default: nil
  attr :class, :string, default: nil

  def avatar(assigns) do
    assigns =
      assigns
      |> assign(:size_class, Map.fetch!(@sizes, assigns.size))
      |> assign(:aria_label, assigns[:"aria-label"])

    ~H"""
    <span
      data-slot="avatar"
      data-size={@size}
      role={if !@src and (@fallback || @aria_label), do: "img"}
      aria-label={if !@src, do: @aria_label || @fallback}
      class={[
        "group/avatar relative inline-flex shrink-0 overflow-hidden rounded-full select-none",
        @size_class,
        @class
      ]}
    >
      <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>
      <img
        :if={@src}
        src={@src}
        srcset={@srcset}
        sizes={@sizes}
        alt={@alt || ""}
        loading={@loading}
        referrerpolicy={@referrerpolicy}
        data-slot="avatar-image"
        onerror="this.style.display='none'"
        class="absolute inset-0 aspect-square size-full object-cover"
      />
    </span>
    """
  end
end

1. Save the file

Tailwind utilities only; inline onerror handler.

2. Use it

index.html
<span data-slot="avatar" data-size="default"
      class="group/avatar relative inline-flex size-8 …">
  <span data-slot="avatar-fallback" class="…">MK</span>
  <img src="/users/mk.jpg" alt="Mehmet K" onerror="this.style.display='none'" class="…">
</span>
View source
index.html
<!--
  shadcn-htmx — raw HTML avatar snippet.

  Mirrors registry/ui/avatar.tsx. Fallback layer + <img> on top; onerror
  hides a broken image so the fallback shows through.
-->

<!-- With image. Optional <img> attrs (MDN): loading="lazy" defers off-screen
     fetches; referrerpolicy avoids leaking the page URL to 3rd-party hosts;
     srcset with an "x" descriptor serves a sharper 2x source on retina. -->
<span data-slot="avatar" data-size="default"
  class="group/avatar relative inline-flex shrink-0 overflow-hidden rounded-full select-none size-8">
  <span data-slot="avatar-fallback"
    class="flex size-full items-center justify-center rounded-full bg-muted text-sm font-medium text-muted-foreground">MK</span>
  <img src="/users/mk.jpg" srcset="/users/[email protected] 2x" alt="Mehmet K" data-slot="avatar-image"
       loading="lazy" referrerpolicy="no-referrer"
       onerror="this.style.display='none'"
       class="absolute inset-0 aspect-square size-full object-cover">
</span>

<!-- Fallback-only (no src) -->
<span data-slot="avatar" data-size="default" role="img" aria-label="Mehmet K"
  class="group/avatar relative inline-flex shrink-0 overflow-hidden rounded-full select-none size-8">
  <span data-slot="avatar-fallback"
    class="flex size-full items-center justify-center rounded-full bg-muted text-sm font-medium text-muted-foreground">MK</span>
</span>

<!-- Group (overlapping stack) -->
<div data-slot="avatar-group" class="group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background">
  <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">
    <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>
  </span>
  <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">
    <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>
  </span>
  <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>
</div>

Examples

Sizes — sm, default, lg

Three sizes; the fallback text scales automatically.

Avatar size is purely a visual concern. Pick sm for dense lists (comments, mentions), default for headers, and lg for profile-style banner-card placements.

MKMKMK
<Avatar size="sm"      fallback="MK" />
<Avatar                fallback="MK" />
<Avatar size="lg"      fallback="MK" />
{{ avatar(fallback="MK", size="sm") }}
{{ avatar(fallback="MK") }}
{{ avatar(fallback="MK", size="lg") }}
{{template "avatar" (dict "Fallback" "MK" "Size" "sm")}}
{{template "avatar" (dict "Fallback" "MK")}}
{{template "avatar" (dict "Fallback" "MK" "Size" "lg")}}
<.avatar size="sm" fallback="MK" />
<.avatar fallback="MK" />
<.avatar size="lg" fallback="MK" />
<div class="flex items-center gap-3">
  <span data-slot="avatar" data-size="sm" role="img" aria-label="Mehmet K" class="group/avatar relative inline-flex shrink-0 overflow-hidden rounded-full select-none size-6 [&amp;_[data-slot=avatar-fallback]]:text-xs">
    <span data-slot="avatar-fallback" class="flex size-full items-center justify-center rounded-full bg-muted text-sm font-medium text-muted-foreground">MK</span>
  </span>
  <span data-slot="avatar" data-size="default" role="img" aria-label="Mehmet K" class="group/avatar relative inline-flex shrink-0 overflow-hidden rounded-full select-none size-8">
    <span data-slot="avatar-fallback" class="flex size-full items-center justify-center rounded-full bg-muted text-sm font-medium text-muted-foreground">MK</span>
  </span>
  <span data-slot="avatar" data-size="lg" role="img" aria-label="Mehmet K" class="group/avatar relative inline-flex shrink-0 overflow-hidden rounded-full select-none size-10">
    <span data-slot="avatar-fallback" class="flex size-full items-center justify-center rounded-full bg-muted text-sm font-medium text-muted-foreground">MK</span>
  </span>
</div>

Further reading

Fallback — works even when src 404s

Pass a broken URL and the fallback shows through. The inline onerror handler hides the broken img element.

Don't fetch the user's photo on the server to check availability — render the <img> and let the browser handle the error. Faster, cheaper, and the platform already has the loading + error state machine for you.

??BrokenMKDRAB
<Avatar src="/broken.jpg" alt="…" fallback="??" />
<Avatar fallback="MK" ariaLabel="Mehmet K" />
{{ avatar(src="/broken.jpg", alt="…", fallback="??") }}
{{ avatar(fallback="MK", aria_label="Mehmet K") }}
{{template "avatar" (dict "Src" "/broken.jpg" "Alt" "…" "Fallback" "??")}}
{{template "avatar" (dict "Fallback" "MK" "AriaLabel" "Mehmet K")}}
<.avatar src="/broken.jpg" alt="…" fallback="??" />
<.avatar fallback="MK" aria-label="Mehmet K" />
<div class="flex items-center gap-3">
  <span data-slot="avatar" data-size="default" class="group/avatar relative inline-flex shrink-0 overflow-hidden rounded-full select-none size-8">
    <span data-slot="avatar-fallback" class="flex size-full items-center justify-center rounded-full bg-muted text-sm font-medium text-muted-foreground">??</span>
    <img src="/this-does-not-exist.jpg" alt="Broken" data-slot="avatar-image" onerror="this.style.display=&#39;none&#39;" class="absolute inset-0 aspect-square size-full object-cover"/>
  </span>
  <span data-slot="avatar" data-size="default" role="img" aria-label="Mehmet K" class="group/avatar relative inline-flex shrink-0 overflow-hidden rounded-full select-none size-8">
    <span data-slot="avatar-fallback" class="flex size-full items-center justify-center rounded-full bg-muted text-sm font-medium text-muted-foreground">MK</span>
  </span>
  <span data-slot="avatar" data-size="default" role="img" aria-label="Dr. R" class="group/avatar relative inline-flex shrink-0 overflow-hidden rounded-full select-none size-8">
    <span data-slot="avatar-fallback" class="flex size-full items-center justify-center rounded-full bg-muted text-sm font-medium text-muted-foreground">DR</span>
  </span>
  <span data-slot="avatar" data-size="default" role="img" aria-label="A. B" class="group/avatar relative inline-flex shrink-0 overflow-hidden rounded-full select-none size-8">
    <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>
  </span>
</div>

Group — overlapping stack with count

Wrap avatars in AvatarGroup; the children overlap via negative margin. Append AvatarGroupCount for the "+N more" tile.

Common pattern for "people on this thread" or "reviewers of this PR". The container uses -space-x-2 to overlap children, and a ring on each child to separate them visually from neighbours.

ABCDEF
+3
<AvatarGroup>
  <Avatar fallback="AB" ariaLabel="A. B" />
  <Avatar fallback="CD" ariaLabel="C. D" />
  <Avatar fallback="EF" ariaLabel="E. F" />
  <AvatarGroupCount>+3</AvatarGroupCount>
</AvatarGroup>
<div data-slot="avatar-group" class="group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background">
  {{ avatar(fallback="AB") }}
  {{ avatar(fallback="CD") }}
  {{ avatar(fallback="EF") }}
  <div data-slot="avatar-group-count" class="…">+3</div>
</div>
<div data-slot="avatar-group" class="…">
  {{template "avatar" (dict "Fallback" "AB")}}
  {{template "avatar" (dict "Fallback" "CD")}}
  <div data-slot="avatar-group-count" class="…">+3</div>
</div>
<div data-slot="avatar-group" class="…">
  <.avatar fallback="AB" />
  <.avatar fallback="CD" />
  <div data-slot="avatar-group-count">+3</div>
</div>
<div data-slot="avatar-group" class="group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background">
  <span data-slot="avatar" data-size="default" role="img" aria-label="A. B" class="group/avatar relative inline-flex shrink-0 overflow-hidden rounded-full select-none size-8">
    <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>
  </span>
  <span data-slot="avatar" data-size="default" role="img" aria-label="C. D" class="group/avatar relative inline-flex shrink-0 overflow-hidden rounded-full select-none size-8">
    <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>
  </span>
  <span data-slot="avatar" data-size="default" role="img" aria-label="E. F" class="group/avatar relative inline-flex shrink-0 overflow-hidden rounded-full select-none size-8">
    <span data-slot="avatar-fallback" class="flex size-full items-center justify-center rounded-full bg-muted text-sm font-medium text-muted-foreground">EF</span>
  </span>
  <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 group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&amp;&gt;svg]:size-4 group-has-data-[size=lg]/avatar-group:[&amp;&gt;svg]:size-5 group-has-data-[size=sm]/avatar-group:[&amp;&gt;svg]:size-3">+3</div>
</div>

API Reference

<Avatar>

PropTypeDefaultDescription
loading"eager" | "lazy"
Native <img> loading attribute, forwarded to the inner image. Use "lazy" to defer fetching avatars that are off-screen in long lists until they near the viewport. Only applies when src is set.
referrerpolicy"no-referrer" | "no-referrer-when-downgrade" | "origin" | "origin-when-cross-origin" | "same-origin" | "strict-origin" | "strict-origin-when-cross-origin" | "unsafe-url"
Native <img> referrerpolicy, forwarded to the inner image. Useful when src is a third-party host (Gravatar/OAuth/CDN); e.g. "no-referrer" avoids leaking the page URL. Only applies when src is set.
srcsetstring
Native <img> srcset, forwarded to the inner image. Primarily for the retina pixel-density case, e.g. "[email protected] 2x"; src acts as the 1x fallback. Only applies when src is set.
sizesstring
Native <img> sizes, forwarded to the inner image. Pair with a width-descriptor srcset; not needed for the common 2x density case. Only applies when src is set.
srcstring
Image URL. Omit for fallback-only avatar.
altstring
Image alt text. Empty for decorative.
fallbackstring
Text shown when src is missing or 404s (typically initials).
size"sm"|"default"|"lg""default"
Visual size.
ariaLabelstring
Accessible name when src is omitted.
classstring
Extra Tailwind classes appended to the root element.