Presence

Availability indicator.

AW Ana W is online MTMarcus T PKPriya K DRDev R SOSam O +3 +2 +1

5 team members active now

html

<div class="presence" data-shown="3" role="status" aria-live="polite">
  <div class="presence__avatars">
    <span class="presence__avatar" style="width:36px;height:36px;background:#A8E6C1;border-color:#FAF7F2;">AW
      <span class="presence__dot presence__dot--online" aria-hidden="true"></span>
      <span class="sr-only">Ana W is online</span>
    </span>
    <span class="presence__avatar" style="width:36px;height:36px;background:#F5C2E0;border-color:#FAF7F2;">MT<span class="sr-only">Marcus T</span></span>
    <span class="presence__avatar" style="width:36px;height:36px;background:#BFD9F2;border-color:#FAF7F2;">PK<span class="sr-only">Priya K</span></span>
    <span class="presence__avatar" style="width:36px;height:36px;background:#D9C8F0;border-color:#FAF7F2;">DR<span class="sr-only">Dev R</span></span>
    <span class="presence__avatar" style="width:36px;height:36px;background:#E8A020;border-color:#FAF7F2;">SO<span class="sr-only">Sam O</span></span>
    <span class="presence__overflow presence__overflow--o2" style="height:36px;">+3</span>
    <span class="presence__overflow presence__overflow--o3" style="height:36px;">+2</span>
    <span class="presence__overflow presence__overflow--o4" style="height:36px;">+1</span>
  </div>
  <p class="presence__label">5 team members active now</p>
</div>
<style>
.presence { font: 13px/1.4 system-ui, sans-serif; color: #241430; max-width: 420px; }
.presence__avatars { display: flex; align-items: center; }
.presence__avatar { position: relative; display: inline-flex; align-items: center; justify-content: center; border-radius: 50%; border: 2px solid; font: 600 12px system-ui, sans-serif; color: #241430; margin-left: -8px; }
.presence__avatar:first-child { margin-left: 0; }
.presence__dot { position: absolute; right: -1px; bottom: -1px; width: 10px; height: 10px; border-radius: 50%; border: 2px solid #FAF7F2; }
.presence__dot--online { background: #34a853; }
.presence__dot--away { background: #E8A020; }
.presence__dot--busy { background: #d93025; }
.presence__dot--offline { background: #24143055; }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
.presence__overflow { display: none; align-items: center; justify-content: center; border-radius: 50%; background: #24143014; color: #241430; font: 600 11px system-ui, sans-serif; margin-left: -8px; width: 36px; padding: 0 4px; box-sizing: border-box; }
[data-shown="2"] .presence__avatar:nth-child(n+3) { display: none; }
[data-shown="2"] .presence__overflow--o2 { display: inline-flex; }
[data-shown="3"] .presence__avatar:nth-child(n+4) { display: none; }
[data-shown="3"] .presence__overflow--o3 { display: inline-flex; }
[data-shown="4"] .presence__avatar:nth-child(n+5) { display: none; }
[data-shown="4"] .presence__overflow--o4 { display: inline-flex; }
.presence__label { margin: 8px 0 0; color: #24143099; }
</style>

tailwind

<div class="presence-tw" data-shown="3" role="status" aria-live="polite">
  <div class="flex items-center">
    <span class="presence-tw__avatar relative inline-flex items-center justify-center rounded-full border-2 font-semibold text-xs text-[#241430]" style="width:36px;height:36px;background:#A8E6C1;border-color:#FAF7F2;margin-left:0;">AW
      <span class="presence-tw__dot presence-tw__dot--online absolute right-[-1px] bottom-[-1px] w-2.5 h-2.5 rounded-full border-2 border-[#FAF7F2]" aria-hidden="true"></span>
      <span class="sr-only">Ana W is online</span>
    </span>
    <span class="presence-tw__avatar relative inline-flex items-center justify-center rounded-full border-2 font-semibold text-xs -ml-2" style="width:36px;height:36px;background:#F5C2E0;border-color:#FAF7F2;">MT<span class="sr-only">Marcus T</span></span>
    <span class="presence-tw__avatar relative inline-flex items-center justify-center rounded-full border-2 font-semibold text-xs -ml-2" style="width:36px;height:36px;background:#BFD9F2;border-color:#FAF7F2;">PK<span class="sr-only">Priya K</span></span>
    <span class="presence-tw__avatar relative inline-flex items-center justify-center rounded-full border-2 font-semibold text-xs -ml-2" style="width:36px;height:36px;background:#D9C8F0;border-color:#FAF7F2;">DR<span class="sr-only">Dev R</span></span>
    <span class="presence-tw__avatar relative inline-flex items-center justify-center rounded-full border-2 font-semibold text-xs -ml-2" style="width:36px;height:36px;background:#E8A020;border-color:#FAF7F2;">SO<span class="sr-only">Sam O</span></span>
    <span class="presence-tw__overflow presence-tw__overflow--o2 -ml-2 items-center justify-center rounded-full bg-[#24143014] text-[#241430] text-[11px] font-semibold w-9 box-border" style="height:36px;">+3</span>
    <span class="presence-tw__overflow presence-tw__overflow--o3 -ml-2 items-center justify-center rounded-full bg-[#24143014] text-[#241430] text-[11px] font-semibold w-9 box-border" style="height:36px;">+2</span>
    <span class="presence-tw__overflow presence-tw__overflow--o4 -ml-2 items-center justify-center rounded-full bg-[#24143014] text-[#241430] text-[11px] font-semibold w-9 box-border" style="height:36px;">+1</span>
  </div>
  <p class="mt-2 text-[#24143099] text-xs">5 team members active now</p>
</div>
<style>
.presence-tw__dot--online { background: #34a853; }
.presence-tw__dot--away { background: #E8A020; }
.presence-tw__dot--busy { background: #d93025; }
.presence-tw__dot--offline { background: #24143055; }
.presence-tw__overflow { display: none; }
[data-shown="2"] .presence-tw__avatar:nth-child(n+3) { display: none; }
[data-shown="2"] .presence-tw__overflow--o2 { display: inline-flex; }
[data-shown="3"] .presence-tw__avatar:nth-child(n+4) { display: none; }
[data-shown="3"] .presence-tw__overflow--o3 { display: inline-flex; }
[data-shown="4"] .presence-tw__avatar:nth-child(n+5) { display: none; }
[data-shown="4"] .presence-tw__overflow--o4 { display: inline-flex; }
</style>

react

export function Presence({
  shown = "3",
  primaryStatus = "online",
  size = 36,
  showLabel = true,
  ringColor = "#FAF7F2",
}) {
  const people = [
    { initials: "AW", name: "Ana W", bg: "#A8E6C1" },
    { initials: "MT", name: "Marcus T", bg: "#F5C2E0" },
    { initials: "PK", name: "Priya K", bg: "#BFD9F2" },
    { initials: "DR", name: "Dev R", bg: "#D9C8F0" },
    { initials: "SO", name: "Sam O", bg: "#E8A020" },
  ];
  const n = Number(shown);
  const visible = people.slice(0, n);
  const overflow = people.length - n;
  const statusColor = { online: "#34a853", away: "#E8A020", busy: "#d93025", offline: "#24143055" }[primaryStatus];
  return (
    <div role="status" aria-live="polite" style={{ font: "13px/1.4 system-ui, sans-serif", color: "#241430", maxWidth: 420 }}>
      <div style={{ display: "flex", alignItems: "center" }}>
        {visible.map((p, i) => (
          <span key={p.initials} style={{ position: "relative", display: "inline-flex", alignItems: "center", justifyContent: "center",
            borderRadius: "50%", border: `2px solid ${ringColor}`, width: size, height: size, background: p.bg,
            fontSize: 12, fontWeight: 600, marginLeft: i === 0 ? 0 : -8 }}>
            {p.initials}
            {i === 0 && <span aria-hidden="true" style={{ position: "absolute", right: -1, bottom: -1, width: 10, height: 10, borderRadius: "50%", border: "2px solid #FAF7F2", background: statusColor }} />}
          </span>
        ))}
        {overflow > 0 && (
          <span style={{ marginLeft: -8, display: "inline-flex", alignItems: "center", justifyContent: "center", borderRadius: "50%",
            background: "#24143014", color: "#241430", fontSize: 11, fontWeight: 600, width: size, height: size, boxSizing: "border-box" }}>
            +{overflow}
          </span>
        )}
      </div>
      {showLabel && <p style={{ margin: "8px 0 0", color: "#24143099" }}>5 team members active now</p>}
    </div>
  );
}