UI Components Easy
Hover Card
A rich popover card that appears on hover over a trigger — shows user profile, link preview, or product info with a small entry delay to prevent flicker.
Open in Lab
MCP
css vanilla-js
Targets: JS HTML
Code
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: Inter, system-ui, sans-serif;
background: #050910;
color: #f2f6ff;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
.demo {
width: 100%;
max-width: 540px;
}
.demo-title {
font-size: 1.5rem;
font-weight: 800;
margin-bottom: 0.375rem;
}
.demo-sub {
color: #475569;
font-size: 0.875rem;
margin-bottom: 2.5rem;
}
.triggers {
display: flex;
flex-direction: column;
gap: 2.5rem;
}
.trigger-line {
font-size: 0.95rem;
color: #94a3b8;
line-height: 1.7;
position: relative;
}
/* ── Trigger ── */
.hc-trigger {
color: #38bdf8;
cursor: pointer;
font-weight: 600;
text-decoration: none;
border-bottom: 1px dashed rgba(56, 189, 248, 0.4);
transition: color 0.15s;
position: relative;
}
.hc-trigger--link {
border-bottom-style: solid;
}
/* ── Card ── */
.hover-card {
position: absolute;
top: calc(100% + 14px);
left: 0;
z-index: 100;
min-width: 260px;
max-width: 300px;
background: #0d1117;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 1rem;
padding: 1rem;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
opacity: 0;
pointer-events: none;
transform: translateY(6px) scale(0.97);
transition: opacity 0.2s ease, transform 0.2s ease;
}
.hover-card.is-visible {
opacity: 1;
pointer-events: auto;
transform: translateY(0) scale(1);
}
/* Arrow */
.hc-arrow {
position: absolute;
top: -6px;
left: 20px;
width: 12px;
height: 12px;
background: #0d1117;
border-top: 1px solid rgba(255, 255, 255, 0.1);
border-left: 1px solid rgba(255, 255, 255, 0.1);
transform: rotate(45deg);
}
/* User card */
.hc-header {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 0.75rem;
}
.hc-avatar {
width: 44px;
height: 44px;
border-radius: 50%;
background: rgba(56, 189, 248, 0.15);
color: #38bdf8;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 0.875rem;
flex-shrink: 0;
}
.hc-name {
font-weight: 700;
font-size: 0.9rem;
color: #f2f6ff;
}
.hc-handle {
font-size: 0.78rem;
color: #475569;
}
.hc-bio {
font-size: 0.8rem;
color: #94a3b8;
line-height: 1.5;
margin-bottom: 0.75rem;
}
.hc-stats {
display: flex;
gap: 1rem;
margin-bottom: 0.875rem;
font-size: 0.78rem;
color: #64748b;
}
.hc-stats strong {
color: #f2f6ff;
}
.hc-btn {
width: 100%;
padding: 0.5rem;
background: #38bdf8;
color: #0f172a;
border: none;
border-radius: 0.5rem;
font-weight: 700;
font-size: 0.8rem;
cursor: pointer;
}
/* Link preview */
.hc-link-preview {
display: flex;
gap: 0.75rem;
align-items: flex-start;
}
.hc-link-thumb {
width: 64px;
height: 48px;
border-radius: 0.5rem;
background: linear-gradient(135deg, #1e293b, #0f172a);
flex-shrink: 0;
}
.hc-link-title {
font-size: 0.82rem;
font-weight: 600;
color: #f2f6ff;
margin-bottom: 0.25rem;
}
.hc-link-desc {
font-size: 0.75rem;
color: #64748b;
line-height: 1.4;
margin-bottom: 0.375rem;
}
.hc-link-url {
font-size: 0.72rem;
color: #38bdf8;
}
/* Product preview */
.hc-product-img {
width: 100%;
height: 120px;
border-radius: 0.625rem;
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
margin-bottom: 0.75rem;
}
.hc-product-name {
font-size: 0.875rem;
font-weight: 600;
margin-bottom: 0.375rem;
}
.hc-product-meta {
display: flex;
align-items: center;
gap: 0.375rem;
margin-bottom: 0.5rem;
}
.hc-stars {
color: #f59e0b;
font-size: 0.75rem;
}
.hc-rating-count {
font-size: 0.72rem;
color: #64748b;
}
.hc-price {
font-size: 1.1rem;
font-weight: 800;
color: #38bdf8;
}
.hc-price-old {
font-size: 0.8rem;
color: #475569;
text-decoration: line-through;
margin-left: 0.375rem;
font-weight: 400;
}
.trigger-line {
position: relative;
}(function () {
var openTimer = null;
var closeTimer = null;
var currentCard = null;
function showCard(card) {
clearTimeout(closeTimer);
if (currentCard && currentCard !== card) {
currentCard.classList.remove("is-visible");
}
currentCard = card;
openTimer = setTimeout(function () {
card.classList.add("is-visible");
}, 200);
}
function hideCard(card) {
clearTimeout(openTimer);
closeTimer = setTimeout(function () {
card.classList.remove("is-visible");
currentCard = null;
}, 150);
}
document.querySelectorAll(".hc-trigger").forEach(function (trigger) {
var cardId = trigger.dataset.hc;
var card = document.getElementById(cardId);
if (!card) return;
trigger.addEventListener("mouseenter", function () {
showCard(card);
});
trigger.addEventListener("mouseleave", function () {
hideCard(card);
});
card.addEventListener("mouseenter", function () {
clearTimeout(closeTimer);
});
card.addEventListener("mouseleave", function () {
hideCard(card);
});
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Hover Card</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="demo">
<h1 class="demo-title">Hover Card</h1>
<p class="demo-sub">Rich preview cards that appear on hover — 200ms open delay.</p>
<div class="triggers">
<!-- User profile hover card -->
<p class="trigger-line">Posted by
<span class="hc-trigger" data-hc="hc-user">@alexkim</span>
· 2 hours ago
</p>
<div class="hover-card" id="hc-user" role="tooltip">
<div class="hc-arrow"></div>
<div class="hc-header">
<div class="hc-avatar">AK</div>
<div>
<p class="hc-name">Alex Kim</p>
<p class="hc-handle">@alexkim</p>
</div>
</div>
<p class="hc-bio">Product designer at Figma. Previously Apple. Writing about design systems and motion.</p>
<div class="hc-stats">
<span><strong>2.4K</strong> Following</span>
<span><strong>18.9K</strong> Followers</span>
</div>
<button class="hc-btn">Follow</button>
</div>
<!-- Link preview -->
<p class="trigger-line">Check out
<a class="hc-trigger hc-trigger--link" data-hc="hc-link" href="#">stealthis.dev →</a>
</p>
<div class="hover-card" id="hc-link" role="tooltip">
<div class="hc-arrow"></div>
<div class="hc-link-preview">
<div class="hc-link-thumb"></div>
<div class="hc-link-info">
<p class="hc-link-title">Stealthis — UI Resource Library</p>
<p class="hc-link-desc">Animations, components, and concept pages for modern web projects.</p>
<p class="hc-link-url">stealthis.dev</p>
</div>
</div>
</div>
<!-- Product preview -->
<p class="trigger-line">Add
<span class="hc-trigger" data-hc="hc-product">Pro Toolkit</span>
to your cart
</p>
<div class="hover-card" id="hc-product" role="tooltip">
<div class="hc-arrow"></div>
<div class="hc-product-img"></div>
<p class="hc-product-name">Pro Toolkit — Design System</p>
<div class="hc-product-meta">
<span class="hc-stars">★★★★★</span>
<span class="hc-rating-count">4.9 (312 reviews)</span>
</div>
<p class="hc-price">$79<span class="hc-price-old">$129</span></p>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>Hover Card
Richer than a tooltip — shows a mini content card on hover with a 200ms open delay to avoid accidental triggers.
Demo variants
- User profile — avatar, name, bio, follower count, follow button
- Link preview — site icon, title, description, URL
- Product preview — image, name, price, star rating
Behaviour
- 200ms open delay prevents flicker when cursor passes over trigger
- Positioned automatically below (or above if near viewport edge)
- Arrow pointer via CSS
::beforeborder trick - Closes when cursor leaves trigger or card