Files
Htmx/docs/Components/Avatar.md
T
2026-05-04 19:57:48 +05:00

4.8 KiB
Raw Blame History

Avatar

A circular user avatar. Shows an image when a src URL is provided; falls back to a text/initials span otherwise.


HTML structure

span.relative.flex.{size classes}.shrink-0.overflow-hidden.rounded-full
  img[src, alt, class]          ← when src is provided
  span.flex.items-center...     ← fallback when no src
    {fallback text}

CSS mechanics

Class Effect
rounded-full overflow-hidden Clips content to a circle
aspect-square h-full w-full object-cover Image fills the circle without distortion
bg-muted text-muted-foreground Neutral background for the initials fallback
Size h-8 w-8 / h-10 w-10 / h-14 w-14 / h-20 w-20 sm / default / lg / xl

Constructor signature

public Avatar(
    string fallback,
    string? src  = null,
    string size  = "default")
Parameter Description
fallback Text shown when no src is given; also used as alt text on the image
src Optional image URL
size "sm" / "default" / "lg" / "xl"

Usage examples

Initials avatar

new Avatar(fallback: "JD")
new Avatar(fallback: "JD", size: "lg")

Image avatar with fallback

new Avatar(fallback: "Jane Doe", src: "/avatars/jane.jpg", size: "default")

Sizes

new Avatar(fallback: "SM", size: "sm")      // 32×32
new Avatar(fallback: "DF", size: "default") // 40×40
new Avatar(fallback: "LG", size: "lg")      // 56×56
new Avatar(fallback: "XL", size: "xl")      // 80×80

Inside a user card

var avatar = new Avatar(fallback: user.Initials, src: user.AvatarUrl, size: "lg");

// In a page's RenderUserCard override:
protected override void RenderUserAvatar(HtmxRenderContext ctx)
    => avatar.Render(ctx.Next());

Tips and tricks

  • Compute initials before constructing the Avatar — the component does not extract them from a full name. See MainLayout's GetInitials helper for a reference implementation.
  • Always provide fallback even when you also provide src — it serves as the alt attribute for accessibility.
  • The Avatar does not handle image load errors. If you need a graceful image fallback on 404, add an onerror="this.style.display='none'" attribute by embedding it in the src or use hxAttrs in a subclassed version.
  • For a group of overlapping avatars (avatar stack), wrap several Avatars in a flex container with negative margin: <div class="flex -space-x-2">.

Complete page example

Templates/ProfilePage.htmx

<div class="max-w-lg mx-auto py-10">
  <div class="flex items-center gap-4 mb-6">
    $$UserAvatar$$
    <div>
      <h1 class="text-xl font-bold">$$DisplayName$$</h1>
      <p class="text-sm text-muted-foreground">$$Email$$</p>
    </div>
  </div>
  <p class="text-sm">Member since $$JoinDate$$</p>
</div>

Templates/ProfilePage.htmx.cs

namespace Htmx.ApiDemo.Templates;

public sealed class ProfilePage : ProfilePageBase
{
    private readonly IHtmxComponent _avatar;
    private readonly byte[] _displayName;
    private readonly byte[] _email;
    private readonly byte[] _joinDate;

    public ProfilePage(AppUser user)
    {
        _avatar = new Components.Avatar(
            fallback: GetInitials(user.DisplayName),
            size: "lg");

        _displayName = (user.DisplayName ?? "Unknown").ToUtf8Bytes();
        _email       = user.Email.ToUtf8Bytes();
        _joinDate    = user.CreatedAt.ToString("MMMM yyyy").ToUtf8Bytes();
    }

    private static string GetInitials(string? name)
    {
        if (string.IsNullOrWhiteSpace(name)) return "?";
        var parts = name.Split(' ', StringSplitOptions.RemoveEmptyEntries);
        return parts.Length >= 2
            ? $"{parts[0][0]}{parts[^1][0]}"
            : name[..1].ToUpperInvariant();
    }

    protected override void RenderUserAvatar(HtmxRenderContext ctx)
        => _avatar.Render(ctx.Next());
    protected override void RenderDisplayName(HtmxRenderContext ctx)
        => ctx.Writer.WriteUtf8(_displayName);
    protected override void RenderEmail(HtmxRenderContext ctx)
        => ctx.Writer.WriteUtf8(_email);
    protected override void RenderJoinDate(HtmxRenderContext ctx)
        => ctx.Writer.WriteUtf8(_joinDate);
}

GET handler

[Handler]
[MapGet("/profile")]
public static partial class GetProfileHandler
{
    public record Query();

    private static async Task<IResult> HandleAsync(
        Query _,
        HttpContext ctx,
        MongoDbService db,
        CancellationToken ct)
    {
        var email = ctx.User.FindFirst(ClaimTypes.Email)?.Value ?? "";
        var user  = await db.FindByNormalizedEmailAsync(email.ToUpperInvariant(), ct);
        if (user is null) return Results.Redirect("/login");
        return await ctx.WriteHtmxPage(new ProfilePage(user), title: "Profile");
    }
}