ツールチップ

上下左右の方向別に出る吹き出し型ツールチップ。CSSの擬似要素とdata属性が中心で、補足説明や用語解説に使えます。

#css#html#accessibility

ライブデモ

使用例(お題: カフェ 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で提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。