/* User menu: a disclosure button (aria-expanded) that opens a panel with a mini profile and account links; Escape, a click
outside or tabbing away closes it. Hover cards: a mini profile that opens on hover or keyboard focus of a name, stays open
while the pointer or focus is inside it, and closes with Escape. */
.um,.hl{--um-accent:#2563eb;--um-ink:#0f172a;--um-muted:#475569;--um-line:#e2e8f0;--um-soft:#f1f5f9;color:var(--um-ink)}
.um :where(p,ul,h3),.hl :where(p,ul,h3,dl,dd){margin:0;padding:0}
.um-av{display:grid;place-items:center;flex:none;width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,#2563eb,#7c3aed);color:#fff;font-size:.8rem;font-weight:700}
/* the bar */
.um-bar{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 12px 10px 18px;background:#fff;border:1px solid var(--um-line);border-radius:16px}
.um-brand{display:flex;align-items:center;gap:8px;font-weight:800;letter-spacing:-.01em}
.um-brand i{width:22px;height:22px;border-radius:7px;background:conic-gradient(from 45deg,#2563eb,#22d3ee,#2563eb)}
.um{position:relative}
.um-btn{display:flex;align-items:center;gap:8px;padding:4px 10px 4px 4px;border:1px solid transparent;border-radius:999px;background:none;color:inherit;font:inherit;font-size:.9rem;font-weight:600;cursor:pointer;transition:background-color .2s}
.um-btn:hover,.um-btn[aria-expanded=true]{background:var(--um-soft)}
.um-btn:focus-visible{outline:3px solid var(--um-accent);outline-offset:2px}
.um-btn svg{width:16px;height:16px;color:var(--um-muted);transition:transform .2s}
.um-btn[aria-expanded=true] svg{transform:rotate(180deg)}
.um-menu{position:absolute;right:0;top:calc(100% + 10px);z-index:10;width:min(300px,calc(100vw - 32px));padding:8px;background:#fff;border:1px solid var(--um-line);border-radius:16px;
box-shadow:0 24px 48px -16px rgba(15,23,42,.28);animation:um-in .18s ease-out}
.um-menu[hidden]{display:none}
@keyframes um-in{from{opacity:0;transform:translateY(-6px)}}
.um-head{display:flex;gap:12px;align-items:center;padding:10px 10px 14px}
.um-head .um-av{width:44px;height:44px;font-size:.95rem}
.um-name{font-weight:700}
.um-mail{margin-top:2px;font-size:.84rem;color:var(--um-muted);overflow-wrap:anywhere}
.um-plan{display:inline-block;margin-top:6px;padding:2px 8px;border-radius:999px;background:#dbeafe;color:#1e40af;font-size:.72rem;font-weight:700}
.um-meter{margin:0 10px 12px;padding:10px 12px;border-radius:12px;background:var(--um-soft);font-size:.8rem;color:var(--um-muted)}
.um-meter b{color:var(--um-ink)}
.um-meter span{display:block;height:6px;margin-top:8px;border-radius:9px;background:#cbd5e1;overflow:hidden}
.um-meter span::before{content:"";display:block;width:72%;height:100%;border-radius:inherit;background:var(--um-accent)}
.um-links{list-style:none;padding:6px 0;border-top:1px solid var(--um-line)}
.um-links a,.um-links button{display:flex;align-items:center;gap:10px;width:100%;padding:9px 10px;border:0;border-radius:10px;background:none;color:inherit;font:inherit;font-size:.9rem;text-align:left;text-decoration:none;cursor:pointer}
.um-links a:hover,.um-links button:hover{background:var(--um-soft)}
.um-links a:focus-visible,.um-links button:focus-visible{outline:2px solid var(--um-accent);outline-offset:-2px}
.um-links svg{flex:none;width:18px;height:18px;color:var(--um-muted)}
.um-links small{margin-left:auto;font-size:.75rem;color:var(--um-muted)}
.um-out{border-top:1px solid var(--um-line);padding-top:6px}
.um-out button{color:#b91c1c}
.um-out button svg{color:#b91c1c}
/* list with presence and hover cards */
.hl{background:#fff;border:1px solid var(--um-line);border-radius:18px;padding:8px 20px}
.hl-title{padding:12px 0 6px;font-size:1rem;font-weight:800}
.hl ul{list-style:none}
.hl li{position:relative;display:flex;align-items:center;gap:12px;padding:12px 0;border-top:1px solid var(--um-line)}
.hl-pic{position:relative;flex:none}
.hl-pic i{position:absolute;right:-1px;bottom:-1px;width:12px;height:12px;border:2px solid #fff;border-radius:50%;background:#94a3b8}
.hl-pic i.on{background:#16a34a}.hl-pic i.busy{background:#d97706}
.hl-who{flex:1;min-width:0}
.hl-role{display:block;margin-top:2px;font-size:.84rem;color:var(--um-muted)}
.hl-state{flex:none;font-size:.8rem;font-weight:600;color:var(--um-muted)}
.hl-state.on{color:#15803d}.hl-state.busy{color:#b45309}
.hc{position:relative;display:inline-block}
@media (max-width:480px){.hc{position:static}.hc-card{left:0;right:0;width:auto}}
.hc-name{color:var(--um-ink);font-weight:700;text-decoration:underline;text-decoration-color:#cbd5e1;text-underline-offset:3px;text-decoration-thickness:2px;border-radius:4px}
.hc-name:hover{text-decoration-color:var(--um-accent)}
.hc-name:focus-visible{outline:3px solid var(--um-accent);outline-offset:2px}
.hc-card{position:absolute;left:-12px;top:calc(100% + 8px);z-index:5;width:min(290px,calc(100vw - 48px));padding:16px;background:#fff;border:1px solid var(--um-line);border-radius:16px;
box-shadow:0 24px 48px -16px rgba(15,23,42,.3);opacity:0;visibility:hidden;transform:translateY(4px);transition:opacity .16s,transform .16s,visibility 0s .16s}
.hc-card::before{content:"";position:absolute;left:0;right:0;top:-10px;height:10px}
.hc:hover .hc-card,.hc:focus-within .hc-card,.hc.is-open .hc-card{opacity:1;visibility:visible;transform:none;transition:opacity .16s .25s,transform .16s .25s,visibility 0s .25s}
.hc:focus-within .hc-card,.hc.is-open .hc-card{transition-delay:0s}
.hc.is-dismissed .hc-card{opacity:0;visibility:hidden}
.hc-top{display:flex;align-items:center;gap:12px}
.hc-top .um-av{width:48px;height:48px;font-size:1rem}
.hc-top b{display:block}
.hc-role{display:block;margin-top:2px;font-size:.82rem;color:var(--um-muted)}
.hc-bio{margin-top:12px;font-size:.86rem;line-height:1.5;color:var(--um-muted)}
.hc-meta{display:flex;gap:18px;margin-top:12px;font-size:.8rem;color:var(--um-muted)}
.hc-meta dd{color:var(--um-ink);font-size:.95rem;font-weight:700}
.hc-meta div{display:flex;flex-direction:column-reverse}
.hc-actions{display:flex;gap:8px;margin-top:14px}
.hc-actions a{flex:1;padding:8px 10px;border:1.5px solid var(--um-accent);border-radius:10px;color:var(--um-accent);font-size:.84rem;font-weight:700;text-align:center;text-decoration:none}
.hc-actions a:first-child{background:var(--um-accent);color:#fff}
.hc-actions a:focus-visible{outline:3px solid var(--um-accent);outline-offset:2px}
.um .sr-only,.hl .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}
.av-b{background:linear-gradient(135deg,#0f766e,#22c55e)}.av-c{background:linear-gradient(135deg,#c2410c,#db2777)}.av-d{background:linear-gradient(135deg,#334155,#64748b)}
@media (prefers-reduced-motion:reduce){.um-menu{animation:none}.um-btn,.um-btn svg,.hc-card{transition:none!important}.hc-card{transform:none}}
JavaScript
/* Account menu: toggle on click; Escape closes it and returns focus to the button; a click outside or tabbing out closes it.
Hover cards: Escape hides an open card until the pointer or focus leaves its name. */
document.querySelectorAll('.um').forEach(function (um) {
var btn = um.querySelector('.um-btn'), menu = um.querySelector('.um-menu');
function set(open) { btn.setAttribute('aria-expanded', String(open)); menu.hidden = !open; }
btn.addEventListener('click', function () { set(menu.hidden); });
um.addEventListener('keydown', function (e) { if (e.key === 'Escape' && !menu.hidden) { set(false); btn.focus(); } });
um.addEventListener('focusout', function (e) { if (e.relatedTarget && !um.contains(e.relatedTarget)) set(false); });
document.addEventListener('click', function (e) { if (!um.contains(e.target)) set(false); });
});
document.querySelectorAll('.hc').forEach(function (hc) {
hc.addEventListener('keydown', function (e) { if (e.key === 'Escape') { hc.classList.add('is-dismissed'); hc.classList.remove('is-open'); } });
hc.addEventListener('mouseleave', function () { if (!hc.contains(document.activeElement)) hc.classList.remove('is-dismissed'); });
hc.addEventListener('focusout', function (e) { if (!hc.contains(e.relatedTarget)) hc.classList.remove('is-dismissed', 'is-open'); });
});
/* User menu: Bootstrap's dropdown (bundle JS) opens a panel with a mini profile and account links; Bootstrap handles Escape,
arrow keys and outside clicks. Hover cards: a mini profile that opens on hover or keyboard focus of a name, stays open
while the pointer or focus is inside it, and closes with Escape. */
.um,.hl,.um-bar{--um-accent:#2563eb;--um-ink:#0f172a;--um-muted:#475569;--um-line:#e2e8f0;--um-soft:#f1f5f9;color:var(--um-ink)}
.um :where(p,ul,h3),.hl :where(p,ul,h3,dl,dd,dt){margin:0}
.hl ul,.hc-meta{padding:0}
.um-av{display:grid;place-items:center;flex:none;width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,#2563eb,#7c3aed);color:#fff;font-size:.8rem;font-weight:700}
/* the bar */
.um-bar{display:flex;align-items:center;justify-content:space-between;gap:12px;color:var(--um-ink);padding:10px 12px 10px 18px;background:#fff;border:1px solid var(--um-line);border-radius:16px}
.um-brand{display:flex;align-items:center;gap:8px;font-weight:800;letter-spacing:-.01em}
.um-brand i{width:22px;height:22px;border-radius:7px;background:conic-gradient(from 45deg,#2563eb,#22d3ee,#2563eb)}
.um-btn{--bs-btn-padding-x:10px;--bs-btn-padding-y:4px;--bs-btn-font-size:.9rem;--bs-btn-font-weight:600;--bs-btn-border-radius:999px;--bs-btn-color:var(--um-ink);
--bs-btn-hover-bg:var(--um-soft);--bs-btn-hover-color:var(--um-ink);--bs-btn-active-bg:var(--um-soft);--bs-btn-active-color:var(--um-ink);--bs-btn-active-border-color:transparent;--bs-btn-focus-box-shadow:none;
display:flex;align-items:center;gap:8px;padding-left:4px;transition:background-color .2s}
.um-btn.show{background:var(--um-soft)}
.um-btn:focus-visible{outline:3px solid var(--um-accent);outline-offset:2px}
.um-btn::after{display:none}
.um-btn svg{width:16px;height:16px;color:var(--um-muted);transition:transform .2s}
.um-btn.show svg{transform:rotate(180deg)}
.um .dropdown-menu{--bs-dropdown-min-width:0;--bs-dropdown-padding-x:8px;--bs-dropdown-padding-y:8px;--bs-dropdown-border-color:var(--um-line);--bs-dropdown-border-radius:16px;--bs-dropdown-spacer:10px;
--bs-dropdown-link-color:var(--um-ink);--bs-dropdown-link-hover-color:var(--um-ink);--bs-dropdown-link-hover-bg:var(--um-soft);--bs-dropdown-link-active-color:var(--um-ink);--bs-dropdown-link-active-bg:#e2e8f0;
--bs-dropdown-item-padding-x:10px;--bs-dropdown-item-padding-y:9px;--bs-dropdown-item-border-radius:10px;--bs-dropdown-divider-bg:var(--um-line);--bs-dropdown-divider-margin-y:6px;--bs-dropdown-font-size:.9rem;
width:min(300px,calc(100vw - 32px));box-shadow:0 24px 48px -16px rgba(15,23,42,.28)}
.um .dropdown-menu.show{animation:um-in .18s ease-out}
@keyframes um-in{from{opacity:0;transform:translateY(-6px)}}
.um-head{display:flex;gap:12px;align-items:center;padding:10px 10px 14px}
.um-head .um-av{width:44px;height:44px;font-size:.95rem}
.um-name{font-weight:700}
.um-mail{margin-top:2px;font-size:.84rem;color:var(--um-muted);overflow-wrap:anywhere}
.um-plan{--bs-badge-padding-x:8px;--bs-badge-padding-y:2px;--bs-badge-font-size:.72rem;--bs-badge-font-weight:700;--bs-badge-color:#1e40af;margin-top:6px;background:#dbeafe;line-height:1.5}
.um-meter{margin:0 10px 6px;padding:10px 12px;border-radius:12px;background:var(--um-soft);font-size:.8rem;color:var(--um-muted)}
.um-meter b{color:var(--um-ink)}
.um-meter .progress{--bs-progress-height:6px;--bs-progress-bg:#cbd5e1;--bs-progress-bar-bg:var(--um-accent);--bs-progress-border-radius:9px;margin-top:8px}
.um .dropdown-item{display:flex;align-items:center;gap:10px;margin:0}
.um .dropdown-item:focus-visible{outline:2px solid var(--um-accent);outline-offset:-2px}
.um .dropdown-item svg{flex:none;width:18px;height:18px;color:var(--um-muted)}
.um .dropdown-item small{margin-left:auto;font-size:.75rem;color:var(--um-muted)}
.um .um-out{color:#b91c1c}
.um .um-out svg{color:#b91c1c}
.um .um-out:hover,.um .um-out:active{color:#b91c1c}
/* list with presence and hover cards */
.hl{--bs-card-border-color:var(--um-line);--bs-card-border-radius:18px;padding:8px 20px}
.hl .hl-title{padding:12px 0 6px;font-size:1rem;font-weight:800;line-height:normal}
.hl ul{list-style:none}
.hl li{position:relative;display:flex;align-items:center;gap:12px;padding:12px 0;border-top:1px solid var(--um-line)}
.hl-pic{position:relative;flex:none}
.hl-pic i{position:absolute;right:-1px;bottom:-1px;width:12px;height:12px;border:2px solid #fff;border-radius:50%;background:#94a3b8}
.hl-pic i.on{background:#16a34a}.hl-pic i.busy{background:#d97706}
.hl-who{flex:1;min-width:0}
.hl-role{display:block;margin-top:2px;font-size:.84rem;color:var(--um-muted)}
.hl-state{flex:none;font-size:.8rem;font-weight:600;color:var(--um-muted)}
.hl-state.on{color:#15803d}.hl-state.busy{color:#b45309}
.hc{position:relative;display:inline-block}
@media (max-width:480px){.hc{position:static}.hc-card{left:0;right:0;width:auto}}
.hc-name{color:var(--um-ink);font-weight:700;text-decoration:underline;text-decoration-color:#cbd5e1;text-underline-offset:3px;text-decoration-thickness:2px;border-radius:4px}
.hc-name:hover{text-decoration-color:var(--um-accent)}
.hc-name:focus-visible{outline:3px solid var(--um-accent);outline-offset:2px}
.hc-card{--bs-card-border-color:var(--um-line);--bs-card-border-radius:16px;position:absolute;left:-12px;top:calc(100% + 8px);z-index:5;width:min(290px,calc(100vw - 48px));padding:16px;background:#fff;border:1px solid var(--um-line);border-radius:16px;
box-shadow:0 24px 48px -16px rgba(15,23,42,.3);opacity:0;visibility:hidden;transform:translateY(4px);transition:opacity .16s,transform .16s,visibility 0s .16s}
.hc-card::before{content:"";position:absolute;left:0;right:0;top:-10px;height:10px}
.hc:hover .hc-card,.hc:focus-within .hc-card,.hc.is-open .hc-card{opacity:1;visibility:visible;transform:none;transition:opacity .16s .25s,transform .16s .25s,visibility 0s .25s}
.hc:focus-within .hc-card,.hc.is-open .hc-card{transition-delay:0s}
.hc.is-dismissed .hc-card{opacity:0;visibility:hidden}
.hc-top{display:flex;align-items:center;gap:12px}
.hc-top .um-av{width:48px;height:48px;font-size:1rem}
.hc-top b{display:block}
.hc-role{display:block;margin-top:2px;font-size:.82rem;color:var(--um-muted)}
.hc-bio{margin-top:12px;font-size:.86rem;line-height:1.5;color:var(--um-muted)}
.hc-meta{display:flex;gap:18px;margin-top:12px;font-size:.8rem;color:var(--um-muted)}
.hc-meta dt{font-weight:400}
.hc-meta dd{color:var(--um-ink);font-size:.95rem;font-weight:700}
.hc-meta div{display:flex;flex-direction:column-reverse}
.hc-actions{display:flex;gap:8px;margin-top:14px}
.hc-actions .btn{--bs-btn-padding-x:10px;--bs-btn-padding-y:8px;--bs-btn-font-size:.84rem;--bs-btn-font-weight:700;--bs-btn-line-height:normal;--bs-btn-border-width:1.5px;--bs-btn-border-radius:10px;--bs-btn-focus-box-shadow:none;flex:1}
.hc-actions .btn-primary{--bs-btn-bg:var(--um-accent);--bs-btn-border-color:var(--um-accent);--bs-btn-hover-bg:#1d4ed8;--bs-btn-hover-border-color:#1d4ed8}
.hc-actions .btn-outline-primary{--bs-btn-color:var(--um-accent);--bs-btn-border-color:var(--um-accent);--bs-btn-hover-bg:var(--um-accent);--bs-btn-hover-border-color:var(--um-accent)}
.hc-actions .btn:focus-visible{outline:3px solid var(--um-accent);outline-offset:2px}
.av-b{background:linear-gradient(135deg,#0f766e,#22c55e)}.av-c{background:linear-gradient(135deg,#c2410c,#db2777)}.av-d{background:linear-gradient(135deg,#334155,#64748b)}
@media (prefers-reduced-motion:reduce){.um .dropdown-menu.show{animation:none}.um-btn,.um-btn svg,.hc-card{transition:none!important}.hc-card{transform:none}}
JavaScript
/* The account menu is Bootstrap's dropdown plugin. Hover cards: Escape hides an open card until the pointer or focus leaves its name. */
document.querySelectorAll('.hc').forEach(function (hc) {
hc.addEventListener('keydown', function (e) { if (e.key === 'Escape') { hc.classList.add('is-dismissed'); hc.classList.remove('is-open'); } });
hc.addEventListener('mouseleave', function () { if (!hc.contains(document.activeElement)) hc.classList.remove('is-dismissed'); });
hc.addEventListener('focusout', function (e) { if (!hc.contains(e.relatedTarget)) hc.classList.remove('is-dismissed', 'is-open'); });
});
/* Account menu: toggle on click; Escape closes it and returns focus to the button; a click outside or tabbing out closes it.
Hover cards: Escape hides an open card until the pointer or focus leaves its name. */
document.querySelectorAll('.um').forEach(function (um) {
var btn = um.querySelector('.um-btn'), menu = um.querySelector('.um-menu');
function set(open) { btn.setAttribute('aria-expanded', String(open)); menu.hidden = !open; }
btn.addEventListener('click', function () { set(menu.hidden); });
um.addEventListener('keydown', function (e) { if (e.key === 'Escape' && !menu.hidden) { set(false); btn.focus(); } });
um.addEventListener('focusout', function (e) { if (e.relatedTarget && !um.contains(e.relatedTarget)) set(false); });
document.addEventListener('click', function (e) { if (!um.contains(e.target)) set(false); });
});
document.querySelectorAll('.hc').forEach(function (hc) {
hc.addEventListener('keydown', function (e) { if (e.key === 'Escape') { hc.classList.add('is-dismissed'); hc.classList.remove('is-open'); } });
hc.addEventListener('mouseleave', function () { if (!hc.contains(document.activeElement)) hc.classList.remove('is-dismissed'); });
hc.addEventListener('focusout', function (e) { if (!hc.contains(e.relatedTarget)) hc.classList.remove('is-dismissed', 'is-open'); });
});
About this set
The small profile views an app shows everywhere. The account menu is a header bar with an avatar button that opens a panel with the user’s name, email and plan, a storage meter, links to the profile, settings, billing and workspaces, and Sign out. The button is a disclosure with aria-expanded; Escape closes the panel and returns focus to the button, and a click outside or tabbing away closes it too. The team list with hover cards shows who is in a document, each with an initials avatar, a presence dot, what they are doing and a status word such as Online or In a meeting. Hovering or focusing a name opens a mini profile with their role, local time, a short note, three counts and View profile and Message links. The card opens after a short delay on hover and at once on keyboard focus, stays open while the pointer or focus is inside it, and Escape dismisses it, which meets the hover content rules in WCAG 1.4.13. The Bootstrap version uses Bootstrap’s dropdown plugin for the menu.
What’s included
Account menu with a mini profile, storage meter and Sign out
Hover cards that also open on keyboard focus
Escape closes the menu and dismisses hover cards
Presence shown as text, not only as a coloured dot
Bootstrap version built on .dropdown-menu and .dropdown-item
Accessibility
The menu button reports aria-expanded and its name includes the user's full name. Hover cards are part of the focus order right after the name that opens them, so their links can be reached with Tab, and Escape hides them. Presence is written out as text next to each dot. Every control shows a visible focus outline, and the menu animation and card transitions stop under prefers-reduced-motion.
Customise it
Change --um-accent, --um-ink, --um-muted, --um-line and --um-soft on .um and .hl, and the avatar gradients in .um-av and the av-* classes. In Bootstrap the menu colours go through the --bs-dropdown-* variables; in Tailwind edit the colour utilities and the um-in animation in the theme block.