06Delayed show on hover
The tooltip waits a beat before appearing, so it stays hidden during a quick pass of the pointer.
Best forDense icon toolbars, an icon repeated on every row of a list
@keyframes delay-loop짧은 등장긴 유지

<main class="stage">
<div class="tip-wrap">
<button class="save-btn" type="button" aria-describedby="tip06">Save</button>
<span class="tooltip is-demo" role="tooltip" id="tip06">Changes save automatically</span>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 숨김→표시→숨김 한 주기 (지연 포함)
$easing: $ease-out;
$color: $subject-ink; // 버튼 색 (노란 무대라 먹색)
@include stage($stage-yellow);
.tip-wrap { position: relative; }
.save-btn {
border: 0;
border-radius: $r-control;
padding: 12px 26px;
font-weight: 700;
font-size: 15px;
line-height: 1;
font-family: inherit;
color: $stage-yellow;
background: $color;
cursor: pointer;
}
.tooltip {
position: absolute;
bottom: calc(100% + 8px);
left: 50%;
transform-origin: bottom center; // 규칙8: 버튼 쪽 아래변 축 — 흔들림의 회전 중심
transform: translateX(-50%) scale(.85);
opacity: 0;
background: $stage-ink;
color: $stage-yellow;
padding: 8px 14px;
border-radius: $r-control;
font-size: clamp(12px, 3.4vw, 14px);
max-width: clamp(140px, 42vw, 220px);
white-space: nowrap; // 한글은 글자마다 줄바꿈 후보라, 없으면 세로로 한 글자씩 쪼개진다
text-align: center;
pointer-events: none;
}
// 마우스를 올려도 곧바로 뜨지 않는다 — 25%(≈500ms) 까지 숨겨 두고, 뜬 뒤엔 길게 유지한다
// 지연 뒤 등장에 스프링 한 겹 — 스케일 .85 → 1.12 오버슛 → 감쇠 반동, 회전은 아래변 축으로 2.4° → -1.4° → .5° 감쇠
@keyframes delay-loop {
0%, 20% { opacity: 0; transform: translateX(-50%) scale(.85) rotate(0deg); }
28% { opacity: 1; transform: translateX(-50%) scale(1.12) rotate(2.4deg); }
37% { opacity: 1; transform: translateX(-50%) scale(.95) rotate(-1.4deg); }
45% { opacity: 1; transform: translateX(-50%) scale(1.02) rotate(.5deg); }
51%, 80% { opacity: 1; transform: translateX(-50%) scale(1) rotate(0deg); }
100% { opacity: 0; transform: translateX(-50%) scale(.85) rotate(0deg); }
}
.tooltip.is-demo {
animation: delay-loop $duration $easing infinite;
}
@media (prefers-reduced-motion: reduce) {
.tooltip.is-demo { animation: none; opacity: 1; transform: translateX(-50%) scale(1) rotate(0deg); }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ffd23f;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.tip-wrap {
position: relative;
}
.save-btn {
border: 0;
border-radius: 12px;
padding: 12px 26px;
font-weight: 700;
font-size: 15px;
line-height: 1;
font-family: inherit;
color: #ffd23f;
background: #17141a;
cursor: pointer;
}
.tooltip {
position: absolute;
bottom: calc(100% + 8px);
left: 50%;
transform-origin: bottom center;
transform: translateX(-50%) scale(0.85);
opacity: 0;
background: #17141a;
color: #ffd23f;
padding: 8px 14px;
border-radius: 12px;
font-size: clamp(12px, 3.4vw, 14px);
max-width: clamp(140px, 42vw, 220px);
white-space: nowrap;
text-align: center;
pointer-events: none;
}
@keyframes delay-loop {
0%, 20% {
opacity: 0;
transform: translateX(-50%) scale(0.85) rotate(0deg);
}
28% {
opacity: 1;
transform: translateX(-50%) scale(1.12) rotate(2.4deg);
}
37% {
opacity: 1;
transform: translateX(-50%) scale(0.95) rotate(-1.4deg);
}
45% {
opacity: 1;
transform: translateX(-50%) scale(1.02) rotate(0.5deg);
}
51%, 80% {
opacity: 1;
transform: translateX(-50%) scale(1) rotate(0deg);
}
100% {
opacity: 0;
transform: translateX(-50%) scale(0.85) rotate(0deg);
}
}
.tooltip.is-demo {
animation: delay-loop 2s ease-out infinite;
}
@media (prefers-reduced-motion: reduce) {
.tooltip.is-demo {
animation: none;
opacity: 1;
transform: translateX(-50%) scale(1) rotate(0deg);
}
}import { useRef, useState } from "react";
import type { CSSProperties, ReactNode } from "react";
import styles from "./DelayTooltip.module.scss";
export type DelayTooltipProps = {
label: ReactNode;
text: string;
/** 호버 후 뜨기까지 기다리는 시간 (기본 400ms) */
delay?: number;
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function DelayTooltip({
label,
text,
delay = 400,
duration = 160,
easing = "ease-out",
color = "#17141a",
}: DelayTooltipProps) {
const [open, setOpen] = useState(false);
const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
const start = () => {
timer.current = setTimeout(() => setOpen(true), delay);
};
const cancel = () => {
if (timer.current) clearTimeout(timer.current);
setOpen(false);
};
return (
<span className={styles.wrap} style={vars}>
<button
type="button"
className={styles.btn}
aria-describedby="delay-tooltip"
onMouseEnter={start}
onMouseLeave={cancel}
onFocus={start}
onBlur={cancel}
>
{label}
</button>
<span id="delay-tooltip" role="tooltip" className={styles.tooltip} data-open={open}>
{text}
</span>
</span>
);
}06 Delayed show on hover — Measured area 6.83% · moving frames 13/23Original by GODRICH · SCSS and React ship in the zip · 9 CSS Tooltip Bubbles — one line, copy-paste
The post this demo lives in
9 CSS Tooltip Bubbles — one line, copy-pasteA css tooltip bubble is a small floating label that shows extra text next to a button, link, or icon and disappears agai See all demos in this post


