22 CSS Avatars06 / 22

Pure CSSMIT licensed

CSS Avatar with Online Status Dot

Presence done to spec: a dot at the bottom-right with a ring of surface colour so it reads on any photo, in four states — online, away, busy, offline — where each state is distinguished by an inline-SVG glyph and visually-hidden text, never by hue alone. A real radio group sets your own presence and every dependent surface updates through :has(), with zero JavaScript.

Published

Live Demo
Try it

The code

<div class="av-06">
  <input class="av-06__sr" type="checkbox" id="av-06-dark">
  <label class="av-06__theme" for="av-06-dark">
    <svg class="av-06__i av-06__i--moon" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"><path d="M20.5 14.3A8.4 8.4 0 0 1 9.7 3.5a8.5 8.5 0 1 0 10.8 10.8Z" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></svg>
    <svg class="av-06__i av-06__i--sun" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="4.1" stroke="currentColor" stroke-width="1.7"/><path d="M12 3.2v2M12 18.8v2M3.2 12h2M18.8 12h2M5.7 5.7l1.4 1.4M16.9 16.9l1.4 1.4M18.3 5.7l-1.4 1.4M7.1 16.9l-1.4 1.4" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>
    <span class="av-06__vh">Dark theme</span>
  </label>

  <div class="av-06__stage">
    <section class="av-06__wrap" aria-labelledby="av-06-h">
      <header class="av-06__head">
        <p class="av-06__eyebrow">Workspace · Presence</p>
        <h2 class="av-06__h" id="av-06-h">Status dots</h2>
        <p class="av-06__sub">Shape and text carry the meaning; colour only reinforces it.</p>
      </header>

      <div class="av-06__panel">
        <div class="av-06__me">
          <span class="av-06__fig">
            <img class="av-06__img" src="https://images.unsplash.com/photo-1517841905240-472988babdf9?w=200&auto=format&fit=facearea&facepad=2.5" alt="Elena Vasquez" width="200" height="200" loading="lazy" decoding="async">
            <span class="av-06__dot" aria-hidden="true"><span class="av-06__glyph"></span></span>
          </span>
          <div class="av-06__meinfo">
            <p class="av-06__name">Elena Vasquez</p>
            <p class="av-06__handle">@elena · Product design</p>
            <p class="av-06__say" aria-live="polite">
              <span class="av-06__s av-06__s--online">Online — active now</span>
              <span class="av-06__s av-06__s--away">Away — back in 20 minutes</span>
              <span class="av-06__s av-06__s--busy">Busy — notifications paused</span>
              <span class="av-06__s av-06__s--offline">Offline — appear invisible</span>
            </p>
          </div>
        </div>

        <fieldset class="av-06__picker">
          <legend class="av-06__legend">Set your presence</legend>
          <label class="av-06__opt"><input type="radio" name="av-06-p" id="av-06-online" checked><span class="av-06__optbox"><span class="av-06__mini av-06__mini--online" aria-hidden="true"></span>Online</span></label>
          <label class="av-06__opt"><input type="radio" name="av-06-p" id="av-06-away"><span class="av-06__optbox"><span class="av-06__mini av-06__mini--away" aria-hidden="true"></span>Away</span></label>
          <label class="av-06__opt"><input type="radio" name="av-06-p" id="av-06-busy"><span class="av-06__optbox"><span class="av-06__mini av-06__mini--busy" aria-hidden="true"></span>Busy</span></label>
          <label class="av-06__opt"><input type="radio" name="av-06-p" id="av-06-offline"><span class="av-06__optbox"><span class="av-06__mini av-06__mini--offline" aria-hidden="true"></span>Offline</span></label>
        </fieldset>
      </div>

      <ul class="av-06__team">
        <li class="av-06__member">
          <span class="av-06__fig av-06__fig--sm">
            <img class="av-06__img" src="https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?w=200&auto=format&fit=facearea&facepad=2.5" alt="Jordan Lee" width="200" height="200" loading="lazy" decoding="async">
            <span class="av-06__dot av-06__dot--online" aria-hidden="true"><span class="av-06__glyph"></span></span>
          </span>
          <span class="av-06__who"><span class="av-06__mname">Jordan Lee</span><span class="av-06__mstate">Online<span class="av-06__vh"> — active now</span></span></span>
        </li>
        <li class="av-06__member">
          <span class="av-06__fig av-06__fig--sm">
            <img class="av-06__img" src="https://images.unsplash.com/photo-1524504388940-b1c1722653e1?w=200&auto=format&fit=facearea&facepad=2.5" alt="Hannah Wright" width="200" height="200" loading="lazy" decoding="async">
            <span class="av-06__dot av-06__dot--away" aria-hidden="true"><span class="av-06__glyph"></span></span>
          </span>
          <span class="av-06__who"><span class="av-06__mname">Hannah Wright</span><span class="av-06__mstate">Away<span class="av-06__vh"> — idle 12 minutes</span></span></span>
        </li>
        <li class="av-06__member">
          <span class="av-06__fig av-06__fig--sm">
            <img class="av-06__img" src="https://images.unsplash.com/photo-1508214751196-bcfd4ca60f91?w=200&auto=format&fit=facearea&facepad=2.5" alt="Sam Rivera" width="200" height="200" loading="lazy" decoding="async">
            <span class="av-06__dot av-06__dot--busy" aria-hidden="true"><span class="av-06__glyph"></span></span>
          </span>
          <span class="av-06__who"><span class="av-06__mname">Sam Rivera</span><span class="av-06__mstate">Busy<span class="av-06__vh"> — in a meeting until 4pm</span></span></span>
        </li>
        <li class="av-06__member">
          <span class="av-06__fig av-06__fig--sm">
            <img class="av-06__img" src="https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=200&auto=format&fit=facearea&facepad=2.5" alt="Tom Bergström" width="200" height="200" loading="lazy" decoding="async">
            <span class="av-06__dot av-06__dot--offline" aria-hidden="true"><span class="av-06__glyph"></span></span>
          </span>
          <span class="av-06__who"><span class="av-06__mname">Tom Bergström</span><span class="av-06__mstate">Offline<span class="av-06__vh"> — last seen 2 hours ago</span></span></span>
        </li>
      </ul>
    </section>
  </div>
</div>
@supports (color: oklch(50% .1 250)) {
  .av-06 {
    --page: oklch(97.6% .004 265);
    --panel: oklch(100% 0 0);
    --panel-2: oklch(96.4% .006 265);
    --ink: oklch(20% .022 265);
    --muted: oklch(53% .022 265);
    --line: oklch(92% .008 265);
    --accent: oklch(58% .196 272);
    --online: oklch(66% .18 150);
    --away: oklch(75% .16 80);
    --busy: oklch(58% .21 22);
    --offline: oklch(70% .02 265);
  }
}

.av-06 {
  --page: #f7f8fa;
  --panel: #ffffff;
  --panel-2: #f1f3f7;
  --ink: #10131a;
  --muted: #5f6672;
  --line: #e4e7ee;
  --accent: #4f46e5;
  --glass: rgba(255,255,255,.68);
  --online: #16a34a;
  --away: #f59e0b;
  --busy: #dc2626;
  --offline: #9aa1ad;
  --shadow: 0 1px 2px rgba(16,19,26,.05), 0 16px 36px -20px rgba(16,19,26,.3);
  --r: 18px;
  --c: var(--online);
  width: 100%;
  inline-size: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  position: relative;
  background: var(--page);
  color: var(--ink);
  font-family: system-ui,-apple-system,"Segoe UI",sans-serif;
  -webkit-font-smoothing: antialiased;
}

.av-06 *,
.av-06 *::before,
.av-06 *::after {
  box-sizing: border-box;
}

.av-06__vh {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.av-06__sr {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  margin: 0;
  pointer-events: none;
}

.av-06__theme {
  position: absolute;
  inset-block-start: clamp(.85rem,2.2vw,1.5rem);
  inset-inline-end: clamp(.85rem,2.2vw,1.5rem);
  z-index: 20;
  display: grid;
  place-items: center;
  inline-size: 2.5rem;
  block-size: 2.5rem;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--glass);
  color: var(--ink);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  cursor: pointer;
  transition: translate .2s ease;
}

.av-06__theme::after {
  content: "";
  position: absolute;
  inset: -.4rem;
  border-radius: inherit;
}

.av-06__theme:hover {
  translate: 0 -1px;
}

.av-06__i {
  inline-size: 1.15rem;
  block-size: 1.15rem;
}

.av-06__i--sun {
  display: none;
}

.av-06__sr:focus-visible + .av-06__theme {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.av-06__stage {
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  place-items: center;
  padding: clamp(1.25rem,5vw,4rem);
}

.av-06__wrap {
  inline-size: min(58rem,100%);
  display: flex;
  flex-direction: column;
  gap: clamp(1.25rem,3vw,2rem);
}

.av-06__head {
  display: flex;
  flex-direction: column;
  gap: .4rem;
  padding-inline-end: 3.5rem;
}

.av-06__eyebrow {
  margin: 0;
  font-size: .7rem;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
}

.av-06__h {
  margin: 0;
  font-size: clamp(1.6rem,4.2vw,2.4rem);
  font-weight: 650;
  letter-spacing: -.03em;
  line-height: 1.05;
}

.av-06__sub {
  margin: 0;
  max-inline-size: 36rem;
  font-size: .95rem;
  line-height: 1.55;
  color: var(--muted);
  text-wrap: pretty;
}

.av-06__panel {
  display: grid;
  gap: clamp(1.25rem,3vw,2rem);
  align-items: center;
  grid-template-columns: minmax(0,1fr);
  padding: clamp(1.35rem,3.5vw,2rem);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: var(--shadow);
}

@media (min-width: 46rem) {
  .av-06__panel {
    grid-template-columns: minmax(0,1fr) auto;
  }
}

.av-06__me {
  display: flex;
  align-items: center;
  gap: clamp(.9rem,2.5vw,1.35rem);
}

.av-06__fig {
  --size: clamp(4rem,11vw,5.5rem);
  position: relative;
  display: block;
  inline-size: var(--size);
  aspect-ratio: 1;
  flex: none;
}

.av-06__img {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  border-radius: 50%;
  object-fit: cover;
  object-position: center top;
  background: var(--panel-2);
  box-shadow: 0 0 0 1px rgba(16,19,26,.06);
}

.av-06__dot {
  position: absolute;
  inset-block-end: 4%;
  inset-inline-end: 4%;
  display: grid;
  place-items: center;
  inline-size: 28%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: var(--c);
  box-shadow: 0 0 0 .17em var(--panel);
  transition: background .2s ease;
}

.av-06__glyph {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  border-radius: 50%;
}

.av-06__fig--sm {
  --size: 2.75rem;
}

.av-06__fig--sm .av-06__dot {
  box-shadow: 0 0 0 3px var(--panel);
}

.av-06__dot--online {
  --c: var(--online);
}

.av-06__dot--away {
  --c: var(--away);
}

.av-06__dot--busy {
  --c: var(--busy);
}

.av-06__dot--offline {
  --c: var(--offline);
}

.av-06__dot--away .av-06__glyph {
  background: radial-gradient(circle at 118% 50%, var(--panel) 46%, transparent 47%);
}

.av-06__dot--busy .av-06__glyph {
  background: linear-gradient(var(--panel), var(--panel)) center/64% 22% no-repeat;
}

.av-06__dot--offline .av-06__glyph {
  background: var(--panel);
  scale: .42;
}

.av-06__meinfo {
  display: flex;
  flex-direction: column;
  gap: .2rem;
  min-inline-size: 0;
}

.av-06__name {
  margin: 0;
  font-size: clamp(1.05rem,2.5vw,1.3rem);
  font-weight: 650;
  letter-spacing: -.02em;
}

.av-06__handle {
  margin: 0;
  font-size: .85rem;
  color: var(--muted);
}

.av-06__say {
  margin: .3rem 0 0;
  font-size: .85rem;
  font-weight: 600;
}

.av-06__s {
  display: none;
}

.av-06__s--online {
  color: var(--online);
}

.av-06__s--away {
  color: var(--away);
}

.av-06__s--busy {
  color: var(--busy);
}

.av-06__s--offline {
  color: var(--muted);
}

.av-06__picker {
  display: grid;
  gap: .3rem;
  margin: 0;
  padding: .35rem;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--panel-2);
}

.av-06__legend {
  padding: .15rem .5rem;
  font-size: .68rem;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
}

.av-06__opt {
  position: relative;
  display: block;
}

.av-06__opt input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  margin: 0;
}

.av-06__optbox {
  display: flex;
  align-items: center;
  gap: .6rem;
  min-block-size: 2.75rem;
  padding: 0 1rem 0 .75rem;
  border-radius: 10px;
  font-size: .88rem;
  font-weight: 550;
  cursor: pointer;
  color: var(--muted);
  transition: background .18s ease, color .18s ease;
}

.av-06__opt:hover .av-06__optbox {
  background: var(--panel);
}

.av-06__opt input:checked + .av-06__optbox {
  background: var(--panel);
  color: var(--ink);
  box-shadow: 0 1px 2px rgba(16,19,26,.08);
}

.av-06__opt input:focus-visible + .av-06__optbox {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.av-06__mini {
  inline-size: .7rem;
  block-size: .7rem;
  border-radius: 50%;
  flex: none;
}

.av-06__mini--online {
  background: var(--online);
}

.av-06__mini--away {
  background: var(--away);
  box-shadow: inset -.2rem 0 0 var(--panel-2);
}

.av-06__mini--busy {
  background: var(--busy) linear-gradient(var(--panel-2), var(--panel-2)) center/60% 20% no-repeat;
}

.av-06__mini--offline {
  background: var(--panel-2);
  box-shadow: inset 0 0 0 2px var(--offline);
}

.av-06__team {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: .5rem;
  grid-template-columns: repeat(auto-fit,minmax(13rem,1fr));
}

.av-06__member {
  display: flex;
  align-items: center;
  gap: .8rem;
  padding: .6rem .8rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 14px;
}

.av-06__who {
  display: flex;
  flex-direction: column;
  min-inline-size: 0;
}

.av-06__mname {
  font-size: .9rem;
  font-weight: 600;
  letter-spacing: -.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.av-06__mstate {
  font-size: .78rem;
  color: var(--muted);
}

.av-06:has(#av-06-online:checked) .av-06__s--online {
  display: block;
}

.av-06:has(#av-06-away:checked) .av-06__s--away {
  display: block;
}

.av-06:has(#av-06-busy:checked) .av-06__s--busy {
  display: block;
}

.av-06:has(#av-06-offline:checked) .av-06__s--offline {
  display: block;
}

.av-06:has(#av-06-away:checked) .av-06__me .av-06__dot {
  --c: var(--away);
}

.av-06:has(#av-06-away:checked) .av-06__me .av-06__glyph {
  background: radial-gradient(circle at 118% 50%, var(--panel) 46%, transparent 47%);
}

.av-06:has(#av-06-busy:checked) .av-06__me .av-06__dot {
  --c: var(--busy);
}

.av-06:has(#av-06-busy:checked) .av-06__me .av-06__glyph {
  background: linear-gradient(var(--panel), var(--panel)) center/64% 22% no-repeat;
}

.av-06:has(#av-06-offline:checked) .av-06__me .av-06__dot {
  --c: var(--offline);
}

.av-06:has(#av-06-offline:checked) .av-06__me .av-06__glyph {
  background: var(--panel);
  scale: .42;
}

.av-06:has(#av-06-dark:checked) {
  --page: #0a0b0e;
  --panel: #14161c;
  --panel-2: #1c1f27;
  --ink: #f2f4f9;
  --muted: #98a0b0;
  --line: #262a33;
  --accent: #8b8cf9;
  --glass: rgba(255,255,255,.07);
  --online: #4ade80;
  --away: #fbbf24;
  --busy: #f87171;
  --offline: #6b7280;
  --shadow: 0 1px 2px rgba(0,0,0,.6), 0 20px 44px -24px rgba(0,0,0,.85);
}

.av-06:has(#av-06-dark:checked) .av-06__img {
  box-shadow: 0 0 0 1px rgba(255,255,255,.08);
}

.av-06:has(#av-06-dark:checked) .av-06__i--moon {
  display: none;
}

.av-06:has(#av-06-dark:checked) .av-06__i--sun {
  display: block;
}

@media (prefers-color-scheme: dark) {
  .av-06:not(:has(#av-06-dark:checked)) {
    --page: #0a0b0e;
    --panel: #14161c;
    --panel-2: #1c1f27;
    --ink: #f2f4f9;
    --muted: #98a0b0;
    --line: #262a33;
    --accent: #8b8cf9;
    --glass: rgba(255,255,255,.07);
    --online: #4ade80;
    --away: #fbbf24;
    --busy: #f87171;
    --offline: #6b7280;
    --shadow: 0 1px 2px rgba(0,0,0,.6), 0 20px 44px -24px rgba(0,0,0,.85);
  }

  .av-06:not(:has(#av-06-dark:checked)) .av-06__img {
    box-shadow: 0 0 0 1px rgba(255,255,255,.08);
  }

  .av-06:not(:has(#av-06-dark:checked)) .av-06__i--moon {
    display: none;
  }

  .av-06:not(:has(#av-06-dark:checked)) .av-06__i--sun {
    display: block;
  }
}

@media (prefers-reduced-motion: reduce) {
  .av-06 *,
    .av-06 *::before,
    .av-06 *::after {
    transition: none !important;
    animation: none !important;
  }
}

@media (forced-colors: active) {
  .av-06__dot {
    box-shadow: 0 0 0 2px Canvas;
    background: CanvasText;
  }

  .av-06__opt input:checked + .av-06__optbox {
    background: Highlight;
    color: HighlightText;
  }

  .av-06__panel,
    .av-06__member,
    .av-06__picker,
    .av-06__theme {
    border-color: CanvasText;
  }
}
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Avatar from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: CSS Avatar with Online Status Dot
Source: https://codefronts.com/components/css-avatars/css-avatar-with-online-status-dot/

Presence done to spec: a dot at the bottom-right with a ring of surface colour so it reads on any photo, in four states — online, away, busy, offline — where each state is distinguished by an inline-SVG glyph and visually-hidden text, never by hue alone. A real radio group sets your own presence and every dependent surface updates through :has(), with zero JavaScript.
## HTML
```html
<div class="av-06">
  <input class="av-06__sr" type="checkbox" id="av-06-dark">
  <label class="av-06__theme" for="av-06-dark">
    <svg class="av-06__i av-06__i--moon" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"><path d="M20.5 14.3A8.4 8.4 0 0 1 9.7 3.5a8.5 8.5 0 1 0 10.8 10.8Z" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></svg>
    <svg class="av-06__i av-06__i--sun" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="4.1" stroke="currentColor" stroke-width="1.7"/><path d="M12 3.2v2M12 18.8v2M3.2 12h2M18.8 12h2M5.7 5.7l1.4 1.4M16.9 16.9l1.4 1.4M18.3 5.7l-1.4 1.4M7.1 16.9l-1.4 1.4" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>
    <span class="av-06__vh">Dark theme</span>
  </label>

  <div class="av-06__stage">
    <section class="av-06__wrap" aria-labelledby="av-06-h">
      <header class="av-06__head">
        <p class="av-06__eyebrow">Workspace · Presence</p>
        <h2 class="av-06__h" id="av-06-h">Status dots</h2>
        <p class="av-06__sub">Shape and text carry the meaning; colour only reinforces it.</p>
      </header>

      <div class="av-06__panel">
        <div class="av-06__me">
          <span class="av-06__fig">
            <img class="av-06__img" src="https://images.unsplash.com/photo-1517841905240-472988babdf9?w=200&auto=format&fit=facearea&facepad=2.5" alt="Elena Vasquez" width="200" height="200" loading="lazy" decoding="async">
            <span class="av-06__dot" aria-hidden="true"><span class="av-06__glyph"></span></span>
          </span>
          <div class="av-06__meinfo">
            <p class="av-06__name">Elena Vasquez</p>
            <p class="av-06__handle">@elena · Product design</p>
            <p class="av-06__say" aria-live="polite">
              <span class="av-06__s av-06__s--online">Online — active now</span>
              <span class="av-06__s av-06__s--away">Away — back in 20 minutes</span>
              <span class="av-06__s av-06__s--busy">Busy — notifications paused</span>
              <span class="av-06__s av-06__s--offline">Offline — appear invisible</span>
            </p>
          </div>
        </div>

        <fieldset class="av-06__picker">
          <legend class="av-06__legend">Set your presence</legend>
          <label class="av-06__opt"><input type="radio" name="av-06-p" id="av-06-online" checked><span class="av-06__optbox"><span class="av-06__mini av-06__mini--online" aria-hidden="true"></span>Online</span></label>
          <label class="av-06__opt"><input type="radio" name="av-06-p" id="av-06-away"><span class="av-06__optbox"><span class="av-06__mini av-06__mini--away" aria-hidden="true"></span>Away</span></label>
          <label class="av-06__opt"><input type="radio" name="av-06-p" id="av-06-busy"><span class="av-06__optbox"><span class="av-06__mini av-06__mini--busy" aria-hidden="true"></span>Busy</span></label>
          <label class="av-06__opt"><input type="radio" name="av-06-p" id="av-06-offline"><span class="av-06__optbox"><span class="av-06__mini av-06__mini--offline" aria-hidden="true"></span>Offline</span></label>
        </fieldset>
      </div>

      <ul class="av-06__team">
        <li class="av-06__member">
          <span class="av-06__fig av-06__fig--sm">
            <img class="av-06__img" src="https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?w=200&auto=format&fit=facearea&facepad=2.5" alt="Jordan Lee" width="200" height="200" loading="lazy" decoding="async">
            <span class="av-06__dot av-06__dot--online" aria-hidden="true"><span class="av-06__glyph"></span></span>
          </span>
          <span class="av-06__who"><span class="av-06__mname">Jordan Lee</span><span class="av-06__mstate">Online<span class="av-06__vh"> — active now</span></span></span>
        </li>
        <li class="av-06__member">
          <span class="av-06__fig av-06__fig--sm">
            <img class="av-06__img" src="https://images.unsplash.com/photo-1524504388940-b1c1722653e1?w=200&auto=format&fit=facearea&facepad=2.5" alt="Hannah Wright" width="200" height="200" loading="lazy" decoding="async">
            <span class="av-06__dot av-06__dot--away" aria-hidden="true"><span class="av-06__glyph"></span></span>
          </span>
          <span class="av-06__who"><span class="av-06__mname">Hannah Wright</span><span class="av-06__mstate">Away<span class="av-06__vh"> — idle 12 minutes</span></span></span>
        </li>
        <li class="av-06__member">
          <span class="av-06__fig av-06__fig--sm">
            <img class="av-06__img" src="https://images.unsplash.com/photo-1508214751196-bcfd4ca60f91?w=200&auto=format&fit=facearea&facepad=2.5" alt="Sam Rivera" width="200" height="200" loading="lazy" decoding="async">
            <span class="av-06__dot av-06__dot--busy" aria-hidden="true"><span class="av-06__glyph"></span></span>
          </span>
          <span class="av-06__who"><span class="av-06__mname">Sam Rivera</span><span class="av-06__mstate">Busy<span class="av-06__vh"> — in a meeting until 4pm</span></span></span>
        </li>
        <li class="av-06__member">
          <span class="av-06__fig av-06__fig--sm">
            <img class="av-06__img" src="https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=200&auto=format&fit=facearea&facepad=2.5" alt="Tom Bergström" width="200" height="200" loading="lazy" decoding="async">
            <span class="av-06__dot av-06__dot--offline" aria-hidden="true"><span class="av-06__glyph"></span></span>
          </span>
          <span class="av-06__who"><span class="av-06__mname">Tom Bergström</span><span class="av-06__mstate">Offline<span class="av-06__vh"> — last seen 2 hours ago</span></span></span>
        </li>
      </ul>
    </section>
  </div>
</div>
```
## CSS
```css
@supports (color: oklch(50% .1 250)) {
  .av-06 {
    --page: oklch(97.6% .004 265);
    --panel: oklch(100% 0 0);
    --panel-2: oklch(96.4% .006 265);
    --ink: oklch(20% .022 265);
    --muted: oklch(53% .022 265);
    --line: oklch(92% .008 265);
    --accent: oklch(58% .196 272);
    --online: oklch(66% .18 150);
    --away: oklch(75% .16 80);
    --busy: oklch(58% .21 22);
    --offline: oklch(70% .02 265);
  }
}

.av-06 {
  --page: #f7f8fa;
  --panel: #ffffff;
  --panel-2: #f1f3f7;
  --ink: #10131a;
  --muted: #5f6672;
  --line: #e4e7ee;
  --accent: #4f46e5;
  --glass: rgba(255,255,255,.68);
  --online: #16a34a;
  --away: #f59e0b;
  --busy: #dc2626;
  --offline: #9aa1ad;
  --shadow: 0 1px 2px rgba(16,19,26,.05), 0 16px 36px -20px rgba(16,19,26,.3);
  --r: 18px;
  --c: var(--online);
  width: 100%;
  inline-size: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  position: relative;
  background: var(--page);
  color: var(--ink);
  font-family: system-ui,-apple-system,"Segoe UI",sans-serif;
  -webkit-font-smoothing: antialiased;
}

.av-06 *,
.av-06 *::before,
.av-06 *::after {
  box-sizing: border-box;
}

.av-06__vh {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.av-06__sr {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  margin: 0;
  pointer-events: none;
}

.av-06__theme {
  position: absolute;
  inset-block-start: clamp(.85rem,2.2vw,1.5rem);
  inset-inline-end: clamp(.85rem,2.2vw,1.5rem);
  z-index: 20;
  display: grid;
  place-items: center;
  inline-size: 2.5rem;
  block-size: 2.5rem;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--glass);
  color: var(--ink);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  cursor: pointer;
  transition: translate .2s ease;
}

.av-06__theme::after {
  content: "";
  position: absolute;
  inset: -.4rem;
  border-radius: inherit;
}

.av-06__theme:hover {
  translate: 0 -1px;
}

.av-06__i {
  inline-size: 1.15rem;
  block-size: 1.15rem;
}

.av-06__i--sun {
  display: none;
}

.av-06__sr:focus-visible + .av-06__theme {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.av-06__stage {
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  place-items: center;
  padding: clamp(1.25rem,5vw,4rem);
}

.av-06__wrap {
  inline-size: min(58rem,100%);
  display: flex;
  flex-direction: column;
  gap: clamp(1.25rem,3vw,2rem);
}

.av-06__head {
  display: flex;
  flex-direction: column;
  gap: .4rem;
  padding-inline-end: 3.5rem;
}

.av-06__eyebrow {
  margin: 0;
  font-size: .7rem;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
}

.av-06__h {
  margin: 0;
  font-size: clamp(1.6rem,4.2vw,2.4rem);
  font-weight: 650;
  letter-spacing: -.03em;
  line-height: 1.05;
}

.av-06__sub {
  margin: 0;
  max-inline-size: 36rem;
  font-size: .95rem;
  line-height: 1.55;
  color: var(--muted);
  text-wrap: pretty;
}

.av-06__panel {
  display: grid;
  gap: clamp(1.25rem,3vw,2rem);
  align-items: center;
  grid-template-columns: minmax(0,1fr);
  padding: clamp(1.35rem,3.5vw,2rem);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: var(--shadow);
}

@media (min-width: 46rem) {
  .av-06__panel {
    grid-template-columns: minmax(0,1fr) auto;
  }
}

.av-06__me {
  display: flex;
  align-items: center;
  gap: clamp(.9rem,2.5vw,1.35rem);
}

.av-06__fig {
  --size: clamp(4rem,11vw,5.5rem);
  position: relative;
  display: block;
  inline-size: var(--size);
  aspect-ratio: 1;
  flex: none;
}

.av-06__img {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  border-radius: 50%;
  object-fit: cover;
  object-position: center top;
  background: var(--panel-2);
  box-shadow: 0 0 0 1px rgba(16,19,26,.06);
}

.av-06__dot {
  position: absolute;
  inset-block-end: 4%;
  inset-inline-end: 4%;
  display: grid;
  place-items: center;
  inline-size: 28%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: var(--c);
  box-shadow: 0 0 0 .17em var(--panel);
  transition: background .2s ease;
}

.av-06__glyph {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  border-radius: 50%;
}

.av-06__fig--sm {
  --size: 2.75rem;
}

.av-06__fig--sm .av-06__dot {
  box-shadow: 0 0 0 3px var(--panel);
}

.av-06__dot--online {
  --c: var(--online);
}

.av-06__dot--away {
  --c: var(--away);
}

.av-06__dot--busy {
  --c: var(--busy);
}

.av-06__dot--offline {
  --c: var(--offline);
}

.av-06__dot--away .av-06__glyph {
  background: radial-gradient(circle at 118% 50%, var(--panel) 46%, transparent 47%);
}

.av-06__dot--busy .av-06__glyph {
  background: linear-gradient(var(--panel), var(--panel)) center/64% 22% no-repeat;
}

.av-06__dot--offline .av-06__glyph {
  background: var(--panel);
  scale: .42;
}

.av-06__meinfo {
  display: flex;
  flex-direction: column;
  gap: .2rem;
  min-inline-size: 0;
}

.av-06__name {
  margin: 0;
  font-size: clamp(1.05rem,2.5vw,1.3rem);
  font-weight: 650;
  letter-spacing: -.02em;
}

.av-06__handle {
  margin: 0;
  font-size: .85rem;
  color: var(--muted);
}

.av-06__say {
  margin: .3rem 0 0;
  font-size: .85rem;
  font-weight: 600;
}

.av-06__s {
  display: none;
}

.av-06__s--online {
  color: var(--online);
}

.av-06__s--away {
  color: var(--away);
}

.av-06__s--busy {
  color: var(--busy);
}

.av-06__s--offline {
  color: var(--muted);
}

.av-06__picker {
  display: grid;
  gap: .3rem;
  margin: 0;
  padding: .35rem;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--panel-2);
}

.av-06__legend {
  padding: .15rem .5rem;
  font-size: .68rem;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
}

.av-06__opt {
  position: relative;
  display: block;
}

.av-06__opt input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  margin: 0;
}

.av-06__optbox {
  display: flex;
  align-items: center;
  gap: .6rem;
  min-block-size: 2.75rem;
  padding: 0 1rem 0 .75rem;
  border-radius: 10px;
  font-size: .88rem;
  font-weight: 550;
  cursor: pointer;
  color: var(--muted);
  transition: background .18s ease, color .18s ease;
}

.av-06__opt:hover .av-06__optbox {
  background: var(--panel);
}

.av-06__opt input:checked + .av-06__optbox {
  background: var(--panel);
  color: var(--ink);
  box-shadow: 0 1px 2px rgba(16,19,26,.08);
}

.av-06__opt input:focus-visible + .av-06__optbox {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.av-06__mini {
  inline-size: .7rem;
  block-size: .7rem;
  border-radius: 50%;
  flex: none;
}

.av-06__mini--online {
  background: var(--online);
}

.av-06__mini--away {
  background: var(--away);
  box-shadow: inset -.2rem 0 0 var(--panel-2);
}

.av-06__mini--busy {
  background: var(--busy) linear-gradient(var(--panel-2), var(--panel-2)) center/60% 20% no-repeat;
}

.av-06__mini--offline {
  background: var(--panel-2);
  box-shadow: inset 0 0 0 2px var(--offline);
}

.av-06__team {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: .5rem;
  grid-template-columns: repeat(auto-fit,minmax(13rem,1fr));
}

.av-06__member {
  display: flex;
  align-items: center;
  gap: .8rem;
  padding: .6rem .8rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 14px;
}

.av-06__who {
  display: flex;
  flex-direction: column;
  min-inline-size: 0;
}

.av-06__mname {
  font-size: .9rem;
  font-weight: 600;
  letter-spacing: -.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.av-06__mstate {
  font-size: .78rem;
  color: var(--muted);
}

.av-06:has(#av-06-online:checked) .av-06__s--online {
  display: block;
}

.av-06:has(#av-06-away:checked) .av-06__s--away {
  display: block;
}

.av-06:has(#av-06-busy:checked) .av-06__s--busy {
  display: block;
}

.av-06:has(#av-06-offline:checked) .av-06__s--offline {
  display: block;
}

.av-06:has(#av-06-away:checked) .av-06__me .av-06__dot {
  --c: var(--away);
}

.av-06:has(#av-06-away:checked) .av-06__me .av-06__glyph {
  background: radial-gradient(circle at 118% 50%, var(--panel) 46%, transparent 47%);
}

.av-06:has(#av-06-busy:checked) .av-06__me .av-06__dot {
  --c: var(--busy);
}

.av-06:has(#av-06-busy:checked) .av-06__me .av-06__glyph {
  background: linear-gradient(var(--panel), var(--panel)) center/64% 22% no-repeat;
}

.av-06:has(#av-06-offline:checked) .av-06__me .av-06__dot {
  --c: var(--offline);
}

.av-06:has(#av-06-offline:checked) .av-06__me .av-06__glyph {
  background: var(--panel);
  scale: .42;
}

.av-06:has(#av-06-dark:checked) {
  --page: #0a0b0e;
  --panel: #14161c;
  --panel-2: #1c1f27;
  --ink: #f2f4f9;
  --muted: #98a0b0;
  --line: #262a33;
  --accent: #8b8cf9;
  --glass: rgba(255,255,255,.07);
  --online: #4ade80;
  --away: #fbbf24;
  --busy: #f87171;
  --offline: #6b7280;
  --shadow: 0 1px 2px rgba(0,0,0,.6), 0 20px 44px -24px rgba(0,0,0,.85);
}

.av-06:has(#av-06-dark:checked) .av-06__img {
  box-shadow: 0 0 0 1px rgba(255,255,255,.08);
}

.av-06:has(#av-06-dark:checked) .av-06__i--moon {
  display: none;
}

.av-06:has(#av-06-dark:checked) .av-06__i--sun {
  display: block;
}

@media (prefers-color-scheme: dark) {
  .av-06:not(:has(#av-06-dark:checked)) {
    --page: #0a0b0e;
    --panel: #14161c;
    --panel-2: #1c1f27;
    --ink: #f2f4f9;
    --muted: #98a0b0;
    --line: #262a33;
    --accent: #8b8cf9;
    --glass: rgba(255,255,255,.07);
    --online: #4ade80;
    --away: #fbbf24;
    --busy: #f87171;
    --offline: #6b7280;
    --shadow: 0 1px 2px rgba(0,0,0,.6), 0 20px 44px -24px rgba(0,0,0,.85);
  }

  .av-06:not(:has(#av-06-dark:checked)) .av-06__img {
    box-shadow: 0 0 0 1px rgba(255,255,255,.08);
  }

  .av-06:not(:has(#av-06-dark:checked)) .av-06__i--moon {
    display: none;
  }

  .av-06:not(:has(#av-06-dark:checked)) .av-06__i--sun {
    display: block;
  }
}

@media (prefers-reduced-motion: reduce) {
  .av-06 *,
    .av-06 *::before,
    .av-06 *::after {
    transition: none !important;
    animation: none !important;
  }
}

@media (forced-colors: active) {
  .av-06__dot {
    box-shadow: 0 0 0 2px Canvas;
    background: CanvasText;
  }

  .av-06__opt input:checked + .av-06__optbox {
    background: Highlight;
    color: HighlightText;
  }

  .av-06__panel,
    .av-06__member,
    .av-06__picker,
    .av-06__theme {
    border-color: CanvasText;
  }
}
```

How this works

The ring is what makes a dot readable. Over a dark jacket a grey "offline" dot vanishes; over a bright window a green "online" dot fights the highlight. A spread of surface colour isolates it from whatever is underneath:

.av-06__dot{
  position:absolute; inset-block-end:6%; inset-inline-end:6%;
  inline-size:28%; aspect-ratio:1; border-radius:50%;
  background:var(--c);
  box-shadow:0 0 0 .18em var(--panel);   /* the separator */
}

Sizing the dot as a percentage of the avatar means one size token drives everything — a 24px avatar and a 96px avatar both get a proportional dot without a second rule.

Never colour alone. Green/amber/red/grey is a coincidence of hue for roughly 1 in 12 men. Every state therefore ships a shape and a text equivalent: online is a solid disc, away a crescent, busy a bar, offline a hollow ring, and each avatar carries hidden text that screen readers announce:

<span class="av-06__dot av-06__dot--busy" aria-hidden="true"></span>
<span class="av-06__vh">Busy — in a meeting</span>

The picker is CSS-only. Four radios; the root reads which is checked and rewrites both the colour token and which glyph shows, then the live region below reflects it in words. Radios (not buttons) give arrow-key navigation and a proper group announcement for free:

.av-06:has(#av-06-busy:checked) .av-06__me .av-06__dot{ --c:var(--busy); }
.av-06:has(#av-06-busy:checked) .av-06__saybusy{ display:block; }

Make it yours

  • Move the dot to the top-right by swapping inset-block-end for inset-block-start — top-right is conventional for counts, bottom-right for presence, so don't collide with a badge.
  • Scale the ring with the dot: express it in em against the dot's own font-size, and it stays proportional at every avatar size.
  • Add a fifth state ("in a call") with its own glyph — the pattern is a token plus a glyph, so no structural change is needed.
  • For a squircle or hexagon avatar, keep the dot on the wrapper (see demo 05) — a clip on the photo would erase it.
  • To drive presence from real data, set data-presence on the wrapper server-side and match [data-presence="busy"] — the CSS is identical.
  • If the surface behind the avatar is an image rather than a flat colour, give the dot a 1px inner border as well so it holds an edge.

Gotchas — read before shipping

  • A dot with no separating ring is unreadable on busy photography. The ring must be painted in the actual surface colour, so it has to follow the theme.
  • Colour-only status fails WCAG 1.4.1. Pair every state with a glyph or text — the hidden-text version costs nothing and fixes screen readers too.
  • Don't put the dot on the <img> — images cannot have pseudo-elements, and a clip-path on the photo would clip the dot anyway.
  • Percentage inline-size on the dot resolves against the wrapper, so the wrapper needs an explicit size (it has --size here) or the dot collapses.
  • aria-live regions must exist in the DOM before the change to be announced — keep the region rendered and swap its contents, never insert it on demand.
  • Two decorations at the same corner will overlap: choose bottom-right for presence, top-right for counts (demo 07), and notch the verification tick at the edge (demo 08).

Browser support

ChromeSafariFirefoxEdge
114+17.5+121+114+

Floor set by :has(), oklch(), backdrop-filter, text-wrap as this demo is written. The core technique itself goes back further — Chrome 105+.

:has() (Chrome 105 / Safari 15.4 / Firefox 121) drives the presence picker and the CSS-only theme toggle. color-mix() (Chrome 111 / Safari 16.2 / Firefox 113) is only used for the soft halo and is gated with @supports. Everything else — box-shadow rings, aspect-ratio, percentage sizing — is universally supported.

Techniques used in this demo

Search CodeFronts

Loading…