IDE Function Hover
A typed-signature documentation popover for code editors. Hover the underlined function call to reveal a stacked card — badge, namespace path, signature, prose description, parameter rows, and a docs-shortcut footer — the kind of tooltip every IDE wishes it shipped by default.
IDE Function Hover the 3rd of 21 designs in the 21 CSS Tooltips collection. The design is implemented in pure CSS — no JavaScript required. Copy the HTML and CSS panels below into your project. Because the demo is pure CSS, it works in any framework or templating engine you happen to use. The design honours prefers-reduced-motion and uses real semantic markup, so it ships accessibility-ready out of the box.
Live preview
The code
<div class="ide-stage">
<div class="ide-editor">
<div class="ide-tabs">
<span class="ide-dot"></span><span class="ide-dot"></span><span class="ide-dot"></span>
<span class="ide-tab">orchestrator.ts</span>
</div>
<div class="ide-body">
<div class="ide-gutter">
<div>1</div><div>2</div><div>3</div><div>4</div><div>5</div>
<div>6</div><div>7</div><div>8</div>
</div>
<div class="ide-code">
<div class="ide-line"><span class="ide-cmt">// pipeline assembled at boot</span></div>
<div class="ide-line"><span class="ide-kw">import</span> { <span class="ide-fn">createScheduler</span> } <span class="ide-kw">from</span> <span class="ide-str">"./scheduler"</span>;</div>
<div class="ide-line"> </div>
<div class="ide-line"><span class="ide-kw">const</span> <span class="ide-var">queue</span> = <span class="ide-symbol"><span class="ide-fn">createScheduler</span><span class="ide-tip">
<span class="ide-tip-head">
<span class="ide-tip-badge">FUNCTION</span>
<span class="ide-tip-path"><span>core</span><span class="ide-sep">›</span><span>scheduler</span><span class="ide-sep">›</span><span>createScheduler</span></span>
</span>
<span class="ide-tip-sig"><span class="ide-kw">function</span> <span class="ide-fn">createScheduler</span><<span class="ide-var">T</span>>(<br> <span class="ide-param">opts</span>: <span class="ide-var">SchedulerOptions</span><<span class="ide-var">T</span>><br>): <span class="ide-var">Queue</span><<span class="ide-var">T</span>></span>
<span class="ide-tip-desc">Creates a back-pressured task queue with concurrency control. Items are processed in <code class="ide-tip-code">FIFO</code> order; failing jobs surface to the dead-letter sink.</span>
<span class="ide-tip-params">
<span class="ide-tip-param-row"><span class="ide-pname">concurrency</span><span class="ide-pdesc">Max parallel jobs. Default <code class="ide-tip-code">4</code>.</span></span>
<span class="ide-tip-param-row"><span class="ide-pname">retries</span><span class="ide-pdesc">Per-task retry budget. Default <code class="ide-tip-code">3</code>.</span></span>
<span class="ide-tip-param-row"><span class="ide-pname">onDrain</span><span class="ide-pdesc">Fires once when the queue empties.</span></span>
</span>
<span class="ide-tip-foot">
<span>scheduler.ts · L42</span>
<span><span class="ide-kbd-key">⌘</span><span class="ide-kbd-key">K</span> for docs</span>
</span>
</span></span>({</div>
<div class="ide-line"> <span class="ide-param">concurrency</span>: <span class="ide-num">8</span>,</div>
<div class="ide-line"> <span class="ide-param">retries</span>: <span class="ide-num">3</span>,</div>
<div class="ide-line">});</div>
</div>
</div>
</div>
</div> /* No @import here. Demos use Inter + JetBrains Mono (from
BaseLayout) and Georgia / cursive system fallbacks for the rest.
See top-of-file Fonts comment for the why. */
.ide-stage {
background: #0d1117;
/* Top padding sized so the tooltip (~280px tall, pops up from the
symbol) fully renders inside the gallery card. Without this the
card's overflow:hidden clips the top of the tip. The bottom needs
less room since the editor is anchored to flex-start. */
padding: 300px 28px 48px;
display: flex;
align-items: flex-start;
justify-content: center;
font-family: 'JetBrains Mono', ui-monospace, monospace;
}
.ide-editor {
background: #161b22;
border: 1px solid #30363d;
border-radius: 8px;
width: 100%;
max-width: 560px;
box-shadow: 0 18px 50px -16px rgba(0, 0, 0, 0.7);
}
.ide-tabs {
background: #0d1117;
border-bottom: 1px solid #30363d;
display: flex;
align-items: center;
padding: 0 14px;
height: 32px;
gap: 4px;
border-radius: 8px 8px 0 0;
}
.ide-dot {
width: 9px; height: 9px; border-radius: 50%;
background: #30363d;
}
.ide-dot:nth-child(1) { background: #ff5f57; }
.ide-dot:nth-child(2) { background: #febc2e; }
.ide-dot:nth-child(3) { background: #28c840; }
.ide-tab {
margin-left: 18px;
padding: 5px 12px;
background: #161b22;
border-radius: 6px 6px 0 0;
font-size: 11px;
color: #c9d1d9;
display: inline-flex;
align-items: center;
gap: 7px;
}
.ide-tab::before {
content: ''; width: 4px; height: 4px; border-radius: 50%;
background: #58a6ff;
}
.ide-body {
padding: 18px 0;
font-size: 13px;
/* Use a pixel line-height so the gutter and code columns advance at
the same rate — em-based line-height (1.85) combined with two
different font-sizes drifted them apart by ~2px per row and the
gutter numbers no longer matched their code lines. */
line-height: 22px;
display: flex;
color: #c9d1d9;
}
.ide-gutter {
width: 44px;
text-align: right;
padding-right: 12px;
color: #484f58;
user-select: none;
border-right: 1px solid #21262d;
/* Same font-size as the code column so digits ride the 22px baseline
in step with the code. */
font-size: 13px;
}
.ide-code { padding: 0 16px; flex: 1; min-width: 0; }
/* No white-space: pre on .ide-line. The line uses for visible
indentation, which works under white-space: normal. Setting pre
would honor the source newlines between sibling spans (especially
inside the .ide-symbol that nests the multi-line tooltip markup),
breaking each token onto its own visual row in the try-it iframe. */
.ide-line {}
.ide-kw { color: #ff7b72; }
.ide-fn { color: #d2a8ff; }
.ide-str { color: #a5d6ff; }
.ide-num { color: #79c0ff; }
.ide-cmt { color: #8b949e; font-style: italic; }
.ide-var { color: #79c0ff; }
.ide-param { color: #ffa657; }
.ide-symbol {
position: relative;
cursor: help;
/* Visible-at-rest affordance: a dashed blue underline tells users
"this token has a hover" without needing them to land on it first.
The original (transparent until hover) was a discovery failure —
users had no signal the demo had a hover tooltip at all. */
border-bottom: 1px dashed rgba(88, 166, 255, 0.55);
transition: border-color 0.2s, background 0.2s;
border-radius: 2px;
padding: 0 2px;
}
.ide-symbol::after {
/* Small info dot to the right of the symbol — second discovery cue
in case the underline gets lost in syntax highlighting. */
content: 'ⓘ';
display: inline-block;
font-size: 9px;
color: rgba(88, 166, 255, 0.6);
vertical-align: middle;
margin-left: 3px;
transition: color 0.2s;
}
.ide-symbol:hover {
border-color: #58a6ff;
background: rgba(88, 166, 255, 0.08);
}
.ide-symbol:hover::after {
color: #58a6ff;
}
.ide-tip {
position: absolute;
bottom: calc(100% + 14px);
left: -20px;
width: 380px;
background: linear-gradient(180deg, #1c2128 0%, #161b22 100%);
border: 1px solid #30363d;
border-radius: 6px;
box-shadow:
0 18px 50px -8px rgba(0, 0, 0, 0.75),
0 0 0 1px rgba(88, 166, 255, 0.06),
inset 0 1px 0 rgba(255, 255, 255, 0.04);
opacity: 0;
visibility: hidden;
transform: translateY(8px);
transition:
opacity 0.22s ease,
transform 0.22s cubic-bezier(0.22, 1, 0.36, 1),
visibility 0s linear 0.22s;
z-index: 10;
font-family: 'JetBrains Mono', ui-monospace, monospace;
pointer-events: none;
display: block;
/* Reset two properties the tip inherits from the .ide-line ancestor:
white-space: pre (which would render the source newlines between
spans as visible whitespace inside the tip) and line-height: 1.85
(the editor's loose line-height — fine for code, way too tall
inside the tooltip's prose). */
white-space: normal;
line-height: 1.5;
text-align: left;
}
.ide-symbol:hover .ide-tip {
opacity: 1;
visibility: visible;
transform: translateY(0);
transition-delay: 0s;
}
.ide-tip::after {
content: '';
position: absolute;
top: 100%;
left: 32px;
width: 10px; height: 10px;
background: #161b22;
border-right: 1px solid #30363d;
border-bottom: 1px solid #30363d;
transform: translateY(-50%) rotate(45deg);
}
.ide-tip-head {
padding: 10px 14px 9px;
border-bottom: 1px solid #21262d;
display: flex;
align-items: center;
gap: 10px;
font-size: 11px;
}
.ide-tip-badge {
background: rgba(88, 166, 255, 0.15);
color: #58a6ff;
padding: 2px 7px;
border-radius: 10px;
font-size: 9.5px;
font-weight: 600;
letter-spacing: 0.05em;
}
.ide-tip-path { color: #8b949e; font-size: 11px; }
.ide-sep { color: #484f58; margin: 0 4px; }
.ide-tip-sig {
padding: 12px 14px;
font-size: 12px;
line-height: 1.6;
border-bottom: 1px solid #21262d;
display: block;
/* Re-enable pre here so the multi-line function signature keeps its
indent. The outer .ide-tip reset this to normal so the prose
blocks (description, params, footer) don't render the source-
formatting whitespace between sibling spans. */
white-space: pre;
}
.ide-tip-desc {
padding: 12px 14px;
font-family: 'Inter', system-ui, sans-serif;
font-size: 12px;
line-height: 1.65;
color: #c9d1d9;
border-bottom: 1px solid #21262d;
display: block;
}
.ide-tip-code {
font-family: 'JetBrains Mono', ui-monospace, monospace;
background: rgba(110, 118, 129, 0.2);
color: #ffa657;
padding: 1px 5px;
border-radius: 3px;
font-size: 11px;
}
.ide-tip-params {
padding: 10px 14px;
border-bottom: 1px solid #21262d;
display: block;
}
.ide-tip-param-row {
display: grid;
grid-template-columns: 90px 1fr;
gap: 10px;
font-size: 11.5px;
padding: 3px 0;
line-height: 1.5;
}
.ide-pname { color: #ffa657; }
.ide-pdesc { color: #8b949e; font-family: 'Inter', system-ui, sans-serif; font-size: 11.5px; }
.ide-tip-foot {
padding: 9px 14px;
font-size: 10px;
color: #6e7681;
display: flex;
justify-content: space-between;
align-items: center;
}
.ide-kbd-key {
background: #21262d;
border: 1px solid #30363d;
border-bottom-width: 2px;
color: #c9d1d9;
padding: 1px 6px;
border-radius: 4px;
font-size: 10px;
margin: 0 2px;
display: inline-block;
}