CSS ツールチップ 吹き出しアニメーション9選 一行でコピペOK
css ツールチップ 吹き出しは、ボタンやアイコンにカーソルを合わせると短い説明が浮かび上がる小さな部品です。今回は9種類を選び、位置合わせの基本からCSS Anchor PositioningやPopover APIといった新しい仕組みまで実装しました。
自動再生 · タイルを押すとその項目へ · すべて1つのzip
- 01 基本上部表示
- 02 フェード+拡大ポップ
- 03 スペースがなければ自動反転
- 04 カード型プレビューツールチップ
- 05 矢印の尻尾
- 06 遅延表示
- 07 キーボードフォーカスで表示
- 08 チャットアプリ風吹き出し
- 09 ヘルプパルスドット
掲載順は見た目の華やかさではなく、位置を決める仕組みの難しさに沿っています。まずbottom:100%とtransformで固定表示する基本形から始め(01)、拡大しながら現れるポップな動きを重ね(02)、CSS Anchor Positioningで画面端でも自動的に向きを変える仕組みを試し(03)、Popover APIで最前面レイヤーに載せるカード型へ進みます(04)。続けて矢印付き・遅延表示・キーボード操作対応という実務で効くディテールを積み上げ(05〜07)、最後にチャットアプリ風の吹き出しと、注意を引くパルスドットという実際の場面への転用を並べました(08〜09)。
01基本上部表示
対象の上に吹き出しがひとつ現れて短い説明を出し、マウスが離れると消えます。ボタン横のヘルプアイコンや、表ヘッダーの略語説明によく使われます。
.tooltip {
position: absolute;
bottom: calc(100% + 8px);
left: 50%;
transform: translate(-50%, 4px);
opacity: 0;
background: $subject-cream;
color: $stage-ink;
padding: 8px 14px;
border-radius: $r-control;
white-space: nowrap;
pointer-events: none;
}
.tooltip.is-demo {
animation: tip-loop $duration $easing infinite;
}
02フェード+拡大ポップ
ツールチップが少し縮んだ状態から弾むように拡大して現れます。強調したい新機能の案内や、ツールバーアイコンの説明に向いています。
.tooltip {
position: absolute;
top: calc(100% + 10px);
left: 50%;
transform-origin: top center;
transform: translateX(-50%) scale(.85);
opacity: 0;
}
.tooltip.is-demo {
animation: pop-loop $duration $easing infinite;
}
@keyframes pop-loop {
0%, 100% { opacity: 0; transform: translateX(-50%) scale(.85); }
25%, 55% { opacity: 1; transform: translateX(-50%) scale(1); }
80% { opacity: 0; transform: translateX(-50%) scale(.85); }
}
03スペースがなければ自動反転
anchor-nameとposition-anchorで位置を固定しておくと、画面端に近づいたときブラウザが自動で反対側に反転させます。画面下部の固定バー付近のボタンや、幅の狭いサイドバー内のアイコンに向いています。caniuseのCSS anchor positioning対応表(2026-09-09確認)によると、Chrome・Edgeは125以降で使え、仕様の完全対応は151以降、Safariは27で完全対応、Firefoxは147以降が部分対応です。
.trigger {
anchor-name: --tip03; // 位置の基準として登録する名前
position: absolute;
}
// 対応状況は本文のcaniuseリンクを参照
@supports (position-anchor: --tip03) {
.tooltip {
position: fixed;
position-anchor: --tip03;
position-area: top;
position-try-fallbacks: flip-block; // 上に余白がなければ下へ反転
margin-block: 10px;
}
}
04カード型プレビューツールチップ
Popover APIでツールチップを最前面レイヤーに表示し、画像と説明つきのカード型プレビューを見せます。商品一覧のホバープレビューや、プロフィール名の上に出す要約カードに向いています。MDNのPopover API対応表によると、Chrome 114以降・Safari 17以降・Firefox 125以降で動作し、対応前のブラウザではpopover属性が無視されてカードは通常のposition:absoluteのまま表示されます。
.card {
position: absolute; // 未対応ブラウザ向けのフォールバック位置
top: calc(100% + 12px);
left: 50%;
transform: translateX(-50%);
}
// popover="manual" で最前面レイヤーへ上がると、トリガーとの相対位置が失われる。
// 舞台の高さを基準にした固定値へ座標を組み直す
.card:popover-open {
position: fixed;
top: 150px;
left: 50%;
}
05矢印の尻尾
吹き出しの下に小さな三角の尻尾がつき、どの要素を指しているかが分かります。フォーム入力欄の横の案内や、チャートのデータポイント説明に使います。
.tooltip {
position: absolute;
bottom: calc(100% + 14px);
&::after {
content: "";
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
width: 14px;
height: 8px;
background: $color;
clip-path: polygon(50% 100%, 0 0, 100% 0);
}
}
06遅延表示
マウスを乗せてもすぐには出ず、少し留まったときだけ現れるため、通り過ぎただけでは表示されません。アイコンが密集したツールバーや、リストの各行にあるアイコンに向いています。
.tooltip {
max-width: clamp(140px, 42vw, 220px);
white-space: nowrap; // 折り返しを止めて一行に収める
text-align: center;
}
.tooltip.is-demo {
animation: delay-loop $duration $easing infinite;
}
@keyframes delay-loop {
0%, 20% { opacity: 0; }
40%, 80% { opacity: 1; }
100% { opacity: 0; }
}
07キーボードフォーカスで表示
マウスホバーだけでなくTabキーでフォーカスを移しても同じツールチップが出ます。キーボードのみで操作するユーザー向けのフォームラベル説明や、アクセシブルなアイコンボタンに向いています。aria-describedby属性でツールチップのIDを紐づけているため、スクリーンリーダーにも同じ説明が読み上げられます。
// マウスホバーだけでなく :focus-visible にも同じ規則をつける
// (ボタンをTabで移動しても表示される)
.gear-btn:hover + .tooltip,
.gear-btn:focus-visible + .tooltip {
opacity: 1;
}
.tooltip.is-demo {
animation: tip-loop $duration $easing infinite;
}
@keyframes tip-loop {
0%, 100% { opacity: 0; }
20%, 55% { opacity: 1; }
80% { opacity: 0; }
}
08チャットアプリ風吹き出し
チャットアプリのように丸い吹き出しに尻尾がつき、話しかけているような印象になります。チャットボットの最初の挨拶吹き出しや、オンボーディングガイドの説明に使います。
.bubble {
position: relative;
border-radius: 18px 18px 18px 4px; // 左下だけ角ばって尻尾に見える
transform-origin: bottom left;
transform: scale(.85);
&::after {
content: "";
position: absolute;
left: -6px;
bottom: 0;
width: 12px;
height: 12px;
background: $color;
clip-path: polygon(100% 0, 100% 100%, 0 100%);
}
}
09ヘルプパルスドット
小さな点がゆっくりパルスして、確認しておきたい情報があることを知らせます。新機能バッジや、設定画面のヘルプアイコンに使います。
.dot::before {
content: "";
position: absolute;
inset: -6px;
border-radius: 50%;
background: $color;
opacity: .5;
animation: pulse-ring $duration $easing infinite;
}
@keyframes pulse-ring {
0%, 100% { transform: scale(.6); opacity: .5; }
50% { transform: scale(1.6); opacity: 0; }
}
どこで崩れるか — 落とし穴
9つとも表示と非表示の切り替えをopacityとtransformだけで作っていて、displayの付け替えは使っていません。1つ目の落とし穴は04のPopover APIを組んだときに実際に踏んだもので、トリガーからの相対位置のまま top: calc(100% + 12px) を残していると、カードが最前面レイヤーに上がった瞬間にコンテイニングブロックがボタンからビューポートへ変わり、カードがビューポートの100%+12px下、つまり舞台の外へ押し出されて見えなくなりました。position: fixedに切り替え、舞台の高さを基準にした固定値(top: 150px)で座標を組み直したことで直りました。
2つ目は06と09の吹き出し内の文字を組んだときのことです。max-widthだけ指定してwhite-space: nowrapを外して試すと、日本語は単語の間に区切りのスペースがないため、幅が足りない箇所ならどこでも折り返し候補になり、文字が縦に一文字ずつ積み重なって表示されました。修正済みの9ファイルを開くには uauwf4hn という文字列が必要です。下のカードからzipをダウンロードしてください。
3つ目は03のスペースがなければ自動反転を試したときのことです。position-try-fallbacksは@supports (position-anchor: --tip03)の中でしか効かせていないため、対応前のブラウザではposition: absoluteのフォールバックがそのまま使われ、画面端に近づいても向きは反転せず下固定のままでした。
アクセシビリティ
9つとも prefers-reduced-motion: reduce で表示アニメーションが止まりますが、要素そのものは消えません。01・02・05・06・07のツールチップと08の吹き出しはopacity: 1のまま静止して読める状態で残り、03の反転版もtransformを外して透明度だけで見える状態を保ちます。04のカードはscale(1)の等倍で止まり、09のパルスリングだけopacity: .3の弱い輪として静止し、拡大縮小も止まります。
@media (prefers-reduced-motion: reduce) {
.tooltip.is-demo, .card, .bubble.is-demo,
.dot::before { animation: none !important; }
.tooltip.is-demo, .bubble.is-demo { opacity: 1; transform: none; }
}
このほかのCSSアニメーションはこちらのカテゴリー一覧に、このサイトの成り立ちは紹介ページにまとめています。
FAQ
ツールチップとPopoverの違いは何ですか?
ツールチップはaria-describedby属性で対象の要素と紐づけられた補足説明で、01〜03・05〜09のように多くはposition: absoluteで通常のレイヤーに配置されます。04で使ったPopover APIはpopover属性を持つ要素をブラウザの最前面レイヤーへ引き上げる仕組みで、他の要素の重なり順やoverflow: hiddenの影響を受けずに表示できる点が異なります。
9つを同じページに複数置いても動きは干渉しませんか?
干渉しません。それぞれのアニメーションは自分自身のクラス名にしか結び付いていないため、01のツールチップと09のパルスドットを隣に並べても、片方のanimationがもう片方の要素に影響することはありません。position-anchorやpopoverを使う03・04も、名前と属性がそれぞれの要素に紐づいているので同時設置に問題はありません。
Reactでは何を使ってツールチップを表示しますか?
zipのreact/フォルダの各コンポーネントは、div要素かspan要素にこれまでと同じクラス名を付けているだけで、表示のタイミングとイージングはCSSのanimationと@keyframesが担います。07だけはonFocusとonBlurのイベントを受け取り、フォーカス中かどうかに応じて表示用のクラス名を付け外しします。