This is the code produced by Wappler's Sematic Patterns for this avatar badge
![]()
<span class="rounded-circle bg-primary text-white d-inline-flex align-items-center justify-content-center fw-semibold border border-2 border-white" style="width:32px;height:32px;">AV</span>
This is the code produced for this avatar badge

<img wappler-role="avatar" class="rounded-circle object-fit-cover ms-5" width="48" height="48" src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=128&h=128&fit=crop&auto=format" alt="Avatar" loading="lazy">
What if we simplified this by using a custom element for the first avatar:
<avatar-badge name="Alice Vance" size="32"></avatar-badge>
or
<avatar-badge initials="AV" size="32"></avatar-badge>
Or for the image avatar
<avatar-badge src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=128&h=128&fit=crop&auto=format" name="Alice Vance" size="48" ></avatar-badge>
Or to show that the person is online

<avatar-badge src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=128&h=128&fit=crop&auto=format" name="Alice Vance" size="48" status="online"></avatar-badge>
All that is required is a JS file with the following content:
class AvatarBadge extends HTMLElement {
static get observedAttributes() {
return ["src", "name", "initials", "size", "bg", "text", "border", "shape", "status"];
}
connectedCallback() {
this.render();
}
attributeChangedCallback() {
this.render();
}
getInitials() {
if (this.getAttribute("initials")) {
return this.getAttribute("initials");
}
const name = this.getAttribute("name");
if (!name) return "";
const parts = name.trim().split(/\s+/);
if (parts.length === 1) return parts[0][0].toUpperCase();
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
}
render() {
const src = this.getAttribute("src");
const initials = this.getInitials();
const size = parseInt(this.getAttribute("size"), 10) || 40;
const bg = this.getAttribute("bg") || "primary";
const text = this.getAttribute("text") || "white";
const borderAttr = this.getAttribute("border");
const hasBorder = borderAttr !== null;
const border = (borderAttr && borderAttr.trim()) ? borderAttr.trim() : "white";
const borderClass = hasBorder ? `border border-2 border-${border}` : "";
const shape = this.getAttribute("shape") || "circle"; // circle | rounded | square
const status = (this.getAttribute("status") || "").toLowerCase();
const showStatus = !!status;
const statusColor =
status === "online" ? "success" :
status === "offline" ? "secondary" :
status === "away" ? "warning" :
status === "busy" ? "danger" :
status;
const dotSize = Math.max(10, Math.round(size * 0.28));
const dotHtml = showStatus ? `
<span
class="position-absolute rounded-circle bg-${statusColor} ${borderClass}"
style="width:${dotSize}px;height:${dotSize}px;right:-1px;bottom:-1px;"
aria-hidden="true"
></span>
` : "";
const shapeClass =
shape === "square"
? ""
: shape === "rounded"
? "rounded"
: "rounded-circle";
const aria = this.getAttribute("name") || initials || "avatar";
if (src) {
this.innerHTML = `
<div style="position:relative; width:${size}px; height:${size}px;">
<img
src="${src}"
alt="${aria}"
loading="lazy"
class="${borderClass} ${shapeClass}"
style="position:absolute; inset:0; width:100%; height:100%; object-fit:cover; display:block;"
onload="this.nextElementSibling.style.display='none';"
onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';"
>
<span
class="align-items-center justify-content-center fw-semibold bg-${bg} text-${text} ${borderClass} ${shapeClass}"
style="position:absolute; inset:0; width:100%; height:100%; display:none; align-items:center; justify-content:center;"
aria-label="${aria}"
>${initials}</span>
${dotHtml}
</div>
`;
} else {
this.innerHTML = `
<div style="position:relative; width:${size}px; height:${size}px;">
<span
class="d-flex align-items-center justify-content-center fw-semibold bg-${bg} text-${text} ${borderClass} ${shapeClass}"
style="position:absolute; inset:0; width:100%; height:100%;"
aria-label="${aria}"
>${initials}</span>
${dotHtml}
</div>
`;
}
}
}
if (!customElements.get("avatar-badge")) {
customElements.define("avatar-badge", AvatarBadge);
}
<avatar-badge> supports these attributes (all optional):
src- Image URL/path. If omitted, it renders initials only.
name- Used to compute initials (first + last letter). Also used for
alttext.
- Used to compute initials (first + last letter). Also used for
initials- Explicit initials override. If set, it takes precedence over
name.
- Explicit initials override. If set, it takes precedence over
size- Avatar size in pixels (number). Default:
40.
- Avatar size in pixels (number). Default:
shapecircle(default),rounded,square
bg- Bootstrap background color name for the initials fallback (default:
primary) - Examples:
primary,secondary,success,danger,warning,info,dark,light
- Bootstrap background color name for the initials fallback (default:
text- Bootstrap text color name for initials (default:
white) - Examples:
white,dark,light, etc.
- Bootstrap text color name for initials (default:
border- Border is off by default.
- If the
borderattribute is present, border is enabled. - Value is the Bootstrap border color (default when present but empty:
white). - Example:
border="white"orborder="dark"
status- Adds a bottom-right status dot when set.
- Supported keywords:
onlineāsuccessofflineāsecondaryawayāwarningbusyādanger
- Or pass a Bootstrap color directly (e.g.
status="info").
If there is enough animo for this, I could create a custom extension, but I prefer that the Wappler team included this and like custom elements as part of Wappler