Co-authored-by: Copilot <copilot@github.com>
4.6 KiB
Avatar
A circular user icon. Shows a profile photo when a URL is provided, or falls back to text (typically initials) on a neutral background.
Quick example
// Initials only
new Avatar(fallback: "JD")
// With a profile photo
new Avatar(fallback: "Jane Doe", src: "/avatars/jane.jpg")
All the options
public Avatar(
string fallback,
string? src = null,
string size = "default")
| Parameter | What it does |
|---|---|
fallback |
The text shown when no image is available — typically initials like "JD". Also used as the alt attribute on the image for screen readers. |
src |
URL of the profile photo. If omitted, the fallback is shown. |
size |
How big the circle is: "sm" (32px), "default" (40px), "lg" (56px), "xl" (80px) |
Real-world examples
Initials in different sizes
new Avatar(fallback: "SM", size: "sm") // 32×32 — good for compact lists
new Avatar(fallback: "JD", size: "default") // 40×40 — standard nav bar
new Avatar(fallback: "LG", size: "lg") // 56×56 — profile card header
new Avatar(fallback: "XL", size: "xl") // 80×80 — full profile page
Profile page header
new Avatar(
fallback: user.DisplayName ?? "?",
src: user.AvatarUrl,
size: "xl")
The fallback is always required — even with a src — because it becomes the alt text on the <img> tag.
Overlapping avatar stack (e.g. "3 team members")
Wrap multiple avatars in a flex container with negative spacing:
<div class="flex -space-x-2">
$$Avatar1$$
$$Avatar2$$
$$Avatar3$$
</div>
How it works
The avatar is a <span> clipped to a circle with rounded-full overflow-hidden. When a src is given, an <img> fills the circle using object-cover so the photo does not stretch. When there is no src, a <span> with a muted background shows the fallback text centred inside the circle.
The component does not handle broken image URLs. If you need a fallback when an image 404s, add an onerror attribute in the surrounding HTML.
The Avatar does not extract initials from full names — do that yourself before constructing it. "Jane Doe" → "JD" is two lines of C# and is better kept in your own code.
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");
}
}