ツールチップ
上下左右の方向別に出る吹き出し型ツールチップ。CSSの擬似要素とdata属性が中心で、補足説明や用語解説に使えます。
ライブデモ
使用例(お題: カフェ MOON BREW)
この技法を「カフェ MOON BREW」というテーマのダミーサイトで実際に使った例です。
HTML
<!-- MOON BREW:豆の産地やアレルギー情報をツールチップで補足 -->
<div class="cafe">
<div class="cafe__bar">
<span class="cafe__logo">☕ MOON BREW</span>
<span class="cafe__tag">SINGLE ORIGIN</span>
</div>
<div class="card">
<div class="card__img" style="background-image:url('https://picsum.photos/360/200?random=21')"></div>
<div class="card__body">
<h2 class="card__name">
エチオピア イルガチェフェ
<span class="tip" data-pos="top" data-tip="ベリーのような華やかな酸と、ジャスミンを思わせる香り。">産地ⓘ</span>
</h2>
<p class="card__desc">浅煎りで仕上げた一杯。冷めても甘みが続く、当店人気のシングルオリジンです。</p>
<div class="card__row">
<span class="chip tip" data-pos="top" data-tip="標高1,900m前後の高地栽培。手摘み・水洗式。">高地栽培</span>
<span class="chip tip" data-pos="bottom" data-tip="乳製品・大豆は使用していません。豆乳への変更も可能。">乳・大豆フリー</span>
<span class="chip tip" data-pos="right" data-tip="ホット ¥560 / アイス ¥600(税込)">価格</span>
</div>
<button class="card__order tip" data-pos="left" data-tip="店内・テイクアウトどちらも承ります。">カートに追加</button>
</div>
</div>
</div>
CSS
/* MOON BREW カフェ テーマ */
:root{--cream:#f5ede1;--brown:#2b1d12;--amber:#c98a3b;--line:#e3d6c2;--muted:#7a6450}
*{box-sizing:border-box}
body{
margin:0;min-height:100vh;display:grid;place-items:center;padding:14px;
font-family:"Hiragino Mincho ProN","Segoe UI",serif;
background:var(--cream);color:var(--brown);
}
.cafe{width:min(420px,100%)}
.cafe__bar{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}
.cafe__logo{font-weight:700;letter-spacing:.06em}
.cafe__tag{font-size:.72rem;letter-spacing:.22em;color:var(--amber)}
.card{background:#fffaf2;border:1px solid var(--line);border-radius:16px;overflow:hidden;box-shadow:0 8px 24px rgba(43,29,18,.1)}
.card__img{height:150px;background-size:cover;background-position:center}
.card__body{padding:16px 18px}
.card__name{margin:0 0 6px;font-size:1.15rem;display:flex;align-items:baseline;gap:8px;flex-wrap:wrap}
.card__desc{margin:0 0 14px;font-size:.86rem;line-height:1.7;color:var(--muted)}
.card__row{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:14px}
.chip{padding:5px 11px;border-radius:999px;background:#f1e4d0;color:var(--brown);font-size:.75rem}
.card__order{
appearance:none;border:none;cursor:pointer;font:inherit;
padding:10px 18px;border-radius:10px;background:var(--amber);color:#2b1d12;font-weight:700;
}
/* ツールチップ本体:擬似要素で吹き出しを表示 */
.tip{position:relative;cursor:help}
.tip::before,.tip::after{
position:absolute;opacity:0;pointer-events:none;
transition:opacity .18s,transform .18s;z-index:5;
}
.tip::before{
content:attr(data-tip);
width:180px;padding:8px 11px;border-radius:8px;
background:var(--brown);color:#fdf6ea;font-size:.74rem;line-height:1.5;font-weight:400;
white-space:normal;text-align:left;letter-spacing:.02em;
}
.tip::after{content:"";border:6px solid transparent}
/* 上 */
.tip[data-pos="top"]::before{left:50%;bottom:calc(100% + 10px);transform:translate(-50%,4px)}
.tip[data-pos="top"]::after{left:50%;bottom:calc(100% + 4px);transform:translateX(-50%);border-top-color:var(--brown)}
/* 下 */
.tip[data-pos="bottom"]::before{left:50%;top:calc(100% + 10px);transform:translate(-50%,-4px)}
.tip[data-pos="bottom"]::after{left:50%;top:calc(100% + 4px);transform:translateX(-50%);border-bottom-color:var(--brown)}
/* 右 */
.tip[data-pos="right"]::before{left:calc(100% + 10px);top:50%;transform:translate(-4px,-50%);width:140px}
.tip[data-pos="right"]::after{left:calc(100% + 4px);top:50%;transform:translateY(-50%);border-right-color:var(--brown)}
/* 左 */
.tip[data-pos="left"]::before{right:calc(100% + 10px);top:50%;transform:translate(4px,-50%);width:140px}
.tip[data-pos="left"]::after{right:calc(100% + 4px);top:50%;transform:translateY(-50%);border-left-color:var(--brown)}
.tip:hover::before,.tip:hover::after,.tip:focus-visible::before,.tip:focus-visible::after{
opacity:1;transform:translate(-50%,0);
}
.tip[data-pos="right"]:hover::before,.tip[data-pos="left"]:hover::before{transform:translateY(-50%)}
.tip[data-pos="right"]:hover::after,.tip[data-pos="left"]:hover::after{transform:translateY(-50%)}
@media (prefers-reduced-motion:reduce){.tip::before,.tip::after{transition:none}}
JavaScript
// ツールチップ本体はCSSだけで動作。JSはキーボード操作の補助のみ
// (tabindex付与でフォーカス可能にし、:focus-visibleでも吹き出しを表示)
const tips = document.querySelectorAll('.tip');
tips.forEach((el) => {
if (!el.hasAttribute('tabindex')) el.setAttribute('tabindex', '0');
// スクリーンリーダー向けに補足テキストをラベル化
const text = el.getAttribute('data-tip');
if (text && !el.hasAttribute('aria-label')) {
el.setAttribute('aria-label', text);
}
});
コード
HTML
<!-- ツールチップ:方向別の吹き出し。CSSのみで動作、JSはキーボード対応の補助 -->
<div class="tip-stage">
<h2 class="tip-stage__title">ツールチップ</h2>
<p class="tip-stage__hint">各ボタンにマウスを乗せる/フォーカスすると説明が出ます。</p>
<div class="tip-stage__grid">
<button class="chip" data-tip="上に表示されるヒントです" data-pos="top">Top</button>
<button class="chip" data-tip="右に表示されるヒントです" data-pos="right">Right</button>
<button class="chip" data-tip="下に表示されるヒントです" data-pos="bottom">Bottom</button>
<button class="chip" data-tip="左に表示されるヒントです" data-pos="left">Left</button>
</div>
<p class="tip-inline">
料金には
<span class="chip chip--link" tabindex="0" data-tip="税・送料込みの総額です" data-pos="top">総額表示</span>
が適用されます。
</p>
</div>
CSS
:root{
--bg:#f4f6fb;
--card:#ffffff;
--accent:#4f46e5;
--tip-bg:#1e293b;
--text:#1f2937;
--muted:#6b7280;
}
*{box-sizing:border-box}
body{
margin:0;min-height:100vh;
display:grid;place-items:center;padding:26px 16px;
font-family:"Segoe UI",system-ui,sans-serif;color:var(--text);
background:
radial-gradient(500px 260px at 12% 0%,#e6e9ff,transparent),
radial-gradient(460px 240px at 100% 100%,#ffe6f2,transparent),
var(--bg);
}
.tip-stage{
width:min(440px,100%);text-align:center;
background:var(--card);border-radius:18px;
box-shadow:0 18px 40px -22px rgba(40,30,90,.35);
padding:28px 24px;
}
.tip-stage__title{margin:0 0 4px;font-size:1.18rem}
.tip-stage__hint{margin:0 0 22px;color:var(--muted);font-size:.86rem}
.tip-stage__grid{display:flex;flex-wrap:wrap;gap:12px;justify-content:center}
.chip{
position:relative;
font:inherit;font-weight:600;cursor:pointer;
border:1px solid #e5e7eb;border-radius:11px;
background:#f9fafb;color:var(--text);padding:11px 18px;
transition:border-color .2s,transform .15s;
}
.chip:hover{border-color:var(--accent)}
.chip:active{transform:translateY(1px)}
.chip--link{
display:inline;padding:1px 4px;border:none;border-radius:4px;
color:var(--accent);background:rgba(79,70,229,.1);
text-decoration:underline dotted;cursor:help;
}
.tip-inline{margin:24px 0 0;color:var(--muted);font-size:.95rem}
/* 吹き出し本体(::after)と矢印(::before) */
.chip::after,.chip::before{
position:absolute;opacity:0;pointer-events:none;
transition:opacity .18s ease,transform .18s ease;
z-index:5;
}
.chip::after{
content:attr(data-tip);
width:max-content;max-width:180px;
padding:7px 11px;border-radius:8px;
background:var(--tip-bg);color:#fff;font-size:.78rem;font-weight:500;line-height:1.4;
box-shadow:0 10px 24px -10px rgba(0,0,0,.5);
}
.chip::before{content:"";border:6px solid transparent}
/* 表示トリガー */
.chip:hover::after,.chip:hover::before,
.chip:focus-visible::after,.chip:focus-visible::before,
.chip.is-shown::after,.chip.is-shown::before{opacity:1}
/* --- 方向別レイアウト --- */
.chip[data-pos="top"]::after{left:50%;bottom:calc(100% + 10px);transform:translateX(-50%)}
.chip[data-pos="top"]::before{left:50%;bottom:calc(100% - 2px);transform:translateX(-50%);border-top-color:var(--tip-bg)}
.chip[data-pos="bottom"]::after{left:50%;top:calc(100% + 10px);transform:translateX(-50%)}
.chip[data-pos="bottom"]::before{left:50%;top:calc(100% - 2px);transform:translateX(-50%);border-bottom-color:var(--tip-bg)}
.chip[data-pos="left"]::after{right:calc(100% + 10px);top:50%;transform:translateY(-50%)}
.chip[data-pos="left"]::before{right:calc(100% - 2px);top:50%;transform:translateY(-50%);border-left-color:var(--tip-bg)}
.chip[data-pos="right"]::after{left:calc(100% + 10px);top:50%;transform:translateY(-50%)}
.chip[data-pos="right"]::before{left:calc(100% - 2px);top:50%;transform:translateY(-50%);border-right-color:var(--tip-bg)}
@media (prefers-reduced-motion:reduce){.chip::after,.chip::before{transition:none}}
JavaScript
// ツールチップは基本CSSで動作。JSはタッチ端末向けのタップ表示補助のみ。
const chips = document.querySelectorAll('.chip');
chips.forEach((chip) => {
// タッチで一時表示(他は閉じる)
chip.addEventListener('touchstart', (e) => {
e.preventDefault();
chips.forEach((c) => { if (c !== chip) c.classList.remove('is-shown'); });
chip.classList.toggle('is-shown');
}, { passive: false });
});
// 余白タップで閉じる
document.addEventListener('touchstart', (e) => {
if (!e.target.closest('.chip')) {
chips.forEach((c) => c.classList.remove('is-shown'));
}
}, { passive: true });
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「ツールチップ」の効果を追加してください。
# 追加してほしい効果
ツールチップ(UIコンポーネント)
上下左右の方向別に出る吹き出し型ツールチップ。CSSの擬似要素とdata属性が中心で、補足説明や用語解説に使えます。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- ツールチップ:方向別の吹き出し。CSSのみで動作、JSはキーボード対応の補助 -->
<div class="tip-stage">
<h2 class="tip-stage__title">ツールチップ</h2>
<p class="tip-stage__hint">各ボタンにマウスを乗せる/フォーカスすると説明が出ます。</p>
<div class="tip-stage__grid">
<button class="chip" data-tip="上に表示されるヒントです" data-pos="top">Top</button>
<button class="chip" data-tip="右に表示されるヒントです" data-pos="right">Right</button>
<button class="chip" data-tip="下に表示されるヒントです" data-pos="bottom">Bottom</button>
<button class="chip" data-tip="左に表示されるヒントです" data-pos="left">Left</button>
</div>
<p class="tip-inline">
料金には
<span class="chip chip--link" tabindex="0" data-tip="税・送料込みの総額です" data-pos="top">総額表示</span>
が適用されます。
</p>
</div>
【CSS】
:root{
--bg:#f4f6fb;
--card:#ffffff;
--accent:#4f46e5;
--tip-bg:#1e293b;
--text:#1f2937;
--muted:#6b7280;
}
*{box-sizing:border-box}
body{
margin:0;min-height:100vh;
display:grid;place-items:center;padding:26px 16px;
font-family:"Segoe UI",system-ui,sans-serif;color:var(--text);
background:
radial-gradient(500px 260px at 12% 0%,#e6e9ff,transparent),
radial-gradient(460px 240px at 100% 100%,#ffe6f2,transparent),
var(--bg);
}
.tip-stage{
width:min(440px,100%);text-align:center;
background:var(--card);border-radius:18px;
box-shadow:0 18px 40px -22px rgba(40,30,90,.35);
padding:28px 24px;
}
.tip-stage__title{margin:0 0 4px;font-size:1.18rem}
.tip-stage__hint{margin:0 0 22px;color:var(--muted);font-size:.86rem}
.tip-stage__grid{display:flex;flex-wrap:wrap;gap:12px;justify-content:center}
.chip{
position:relative;
font:inherit;font-weight:600;cursor:pointer;
border:1px solid #e5e7eb;border-radius:11px;
background:#f9fafb;color:var(--text);padding:11px 18px;
transition:border-color .2s,transform .15s;
}
.chip:hover{border-color:var(--accent)}
.chip:active{transform:translateY(1px)}
.chip--link{
display:inline;padding:1px 4px;border:none;border-radius:4px;
color:var(--accent);background:rgba(79,70,229,.1);
text-decoration:underline dotted;cursor:help;
}
.tip-inline{margin:24px 0 0;color:var(--muted);font-size:.95rem}
/* 吹き出し本体(::after)と矢印(::before) */
.chip::after,.chip::before{
position:absolute;opacity:0;pointer-events:none;
transition:opacity .18s ease,transform .18s ease;
z-index:5;
}
.chip::after{
content:attr(data-tip);
width:max-content;max-width:180px;
padding:7px 11px;border-radius:8px;
background:var(--tip-bg);color:#fff;font-size:.78rem;font-weight:500;line-height:1.4;
box-shadow:0 10px 24px -10px rgba(0,0,0,.5);
}
.chip::before{content:"";border:6px solid transparent}
/* 表示トリガー */
.chip:hover::after,.chip:hover::before,
.chip:focus-visible::after,.chip:focus-visible::before,
.chip.is-shown::after,.chip.is-shown::before{opacity:1}
/* --- 方向別レイアウト --- */
.chip[data-pos="top"]::after{left:50%;bottom:calc(100% + 10px);transform:translateX(-50%)}
.chip[data-pos="top"]::before{left:50%;bottom:calc(100% - 2px);transform:translateX(-50%);border-top-color:var(--tip-bg)}
.chip[data-pos="bottom"]::after{left:50%;top:calc(100% + 10px);transform:translateX(-50%)}
.chip[data-pos="bottom"]::before{left:50%;top:calc(100% - 2px);transform:translateX(-50%);border-bottom-color:var(--tip-bg)}
.chip[data-pos="left"]::after{right:calc(100% + 10px);top:50%;transform:translateY(-50%)}
.chip[data-pos="left"]::before{right:calc(100% - 2px);top:50%;transform:translateY(-50%);border-left-color:var(--tip-bg)}
.chip[data-pos="right"]::after{left:calc(100% + 10px);top:50%;transform:translateY(-50%)}
.chip[data-pos="right"]::before{left:calc(100% - 2px);top:50%;transform:translateY(-50%);border-right-color:var(--tip-bg)}
@media (prefers-reduced-motion:reduce){.chip::after,.chip::before{transition:none}}
【JavaScript】
// ツールチップは基本CSSで動作。JSはタッチ端末向けのタップ表示補助のみ。
const chips = document.querySelectorAll('.chip');
chips.forEach((chip) => {
// タッチで一時表示(他は閉じる)
chip.addEventListener('touchstart', (e) => {
e.preventDefault();
chips.forEach((c) => { if (c !== chip) c.classList.remove('is-shown'); });
chip.classList.toggle('is-shown');
}, { passive: false });
});
// 余白タップで閉じる
document.addEventListener('touchstart', (e) => {
if (!e.target.closest('.chip')) {
chips.forEach((c) => c.classList.remove('is-shown'));
}
}, { passive: true });
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。