Hover an avatar and a profile card slides out beside it: photo, name, role and a live status dot. The pieces stagger in with --i delays, and the dot keeps a slow ping going.
Also known as: avatar hover card css, user tooltip on hover, profile popover css.
<span class="tip-user-card">
<button type="button" class="tip-user-card-avatar" aria-label="Maya Ross" aria-describedby="tip-user-card-tip">MR</button>
<span class="tip-user-card-tip" id="tip-user-card-tip" role="tooltip">
<span class="tip-user-card-face" style="--i: 0" aria-hidden="true">MR</span>
<span class="tip-user-card-name" style="--i: 1">Maya Ross</span>
<span class="tip-user-card-role" style="--i: 2">Design lead · Lisbon</span>
<span class="tip-user-card-status" style="--i: 3">Online now</span>
</span>
</span>.tip-user-card {
--c: var(--tip-color, #3b82f6);
font-size: var(--tip-size, 16px);
position: relative;
display: inline-flex;
--face: linear-gradient(135deg, var(--c), color-mix(in srgb, var(--c) 45%, #ec4899));
margin-right: 12.5em; /* room for the card in this preview */
}
.tip-user-card-avatar {
display: grid;
place-items: center;
width: 3em;
height: 3em;
padding: 0;
border: 0;
border-radius: 50%;
background: var(--face);
box-shadow: 0 0 0 0 color-mix(in srgb, var(--c) 30%, transparent);
color: #fff;
font: 700 0.9em system-ui, sans-serif;
letter-spacing: 0.03em;
cursor: pointer;
transition: box-shadow 0.25s ease;
}
.tip-user-card:hover .tip-user-card-avatar { box-shadow: 0 0 0 0.25em color-mix(in srgb, var(--c) 25%, transparent); }
.tip-user-card-avatar:focus-visible {
outline: 2px solid var(--c);
outline-offset: 2px;
}
.tip-user-card-tip {
position: absolute;
top: 50%;
left: calc(100% + 0.9em);
z-index: 10;
display: grid;
grid-template-columns: auto 1fr;
column-gap: 0.7em;
align-items: center;
width: max-content;
padding: 0.8em 0.95em 0.7em 0.8em;
border: 1px solid rgb(15 23 42 / 0.12);
border-radius: 0.8em;
background: #fff;
box-shadow: 0 0.8em 1.6em -0.6em rgb(15 23 42 / 0.35);
color: #0f172a;
font: 400 0.85em/1.35 system-ui, sans-serif;
opacity: 0;
visibility: hidden;
pointer-events: none;
transform-origin: 0 50%;
transform: translate(-0.5em, -50%) scale(0.96);
transition: opacity 0.2s ease, transform 0.35s cubic-bezier(0.2, 0.8, 0.2, 1), visibility 0.35s;
}
.tip-user-card-tip::before {
content: "";
position: absolute;
top: 50%;
right: 100%;
box-sizing: border-box;
width: 0.75em;
height: 0.75em;
border: inherit;
border-top: 0;
border-right: 0;
border-radius: 0 0 0 0.12em;
background: inherit;
transform: translate(55%, -50%) rotate(45deg);
}
.tip-user-card-tip > span {
opacity: 0;
transform: translateY(0.4em);
transition: opacity 0.15s ease, transform 0.15s ease;
}
.tip-user-card-face {
display: grid;
grid-row: span 2;
place-items: center;
width: 2.9em;
height: 2.9em;
border-radius: 50%;
background: var(--face);
color: #fff;
font-size: 0.95em;
font-weight: 700;
}
.tip-user-card-name {
align-self: end;
font-size: 1.05em;
font-weight: 700;
}
.tip-user-card-role {
align-self: start;
color: #64748b;
font-size: 0.9em;
}
.tip-user-card-status {
display: flex;
grid-column: 1 / -1;
align-items: center;
gap: 0.5em;
margin-top: 0.7em;
padding-top: 0.6em;
border-top: 1px solid rgb(15 23 42 / 0.08);
color: #334155;
font-size: 0.85em;
font-weight: 500;
}
.tip-user-card-status::before {
content: "";
width: 0.55em;
height: 0.55em;
border-radius: 50%;
background: #22c55e;
animation: tip-user-card-ping 1.8s ease-out infinite;
}
.tip-user-card:hover .tip-user-card-tip,
.tip-user-card:focus-within .tip-user-card-tip {
opacity: 1;
visibility: visible;
transform: translate(0, -50%) scale(1);
}
.tip-user-card:hover .tip-user-card-tip > span,
.tip-user-card:focus-within .tip-user-card-tip > span {
opacity: 1;
transform: translateY(0);
transition: opacity 0.3s ease, transform 0.4s cubic-bezier(0.2, 0.8, 0.2, 1);
transition-delay: calc(0.08s + var(--i) * 0.06s);
}
@keyframes tip-user-card-ping {
0% { box-shadow: 0 0 0 0 rgb(34 197 94 / 0.55); }
70%, 100% { box-shadow: 0 0 0 0.45em rgb(34 197 94 / 0); }
}
@media (prefers-reduced-motion: reduce) {
.tip-user-card, .tip-user-card *, .tip-user-card::before, .tip-user-card::after, .tip-user-card *::before, .tip-user-card *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}--tip-color on the element or a parent.
Currently previewing #3b82f6.--tip-size. Every other dimension is in
em, so the whole thing scales together. Currently previewing 16px.transition or animation
shorthand. Staggered delays are relative, so they keep their rhythm.