Docs
検索

Tooltip

ホバーやフォーカスに応じて、要素の補足テキストを表示するコンポーネントです。

Overview

Preview
ショートカット: Ctrl + S
<Tooltip.Root tooltipId="tt-01">
<Tooltip.Trigger className="-bd -px:15 -py:5 -bdrs:10">保存</Tooltip.Trigger>
<Tooltip.Popup>ショートカット: Ctrl + S</Tooltip.Popup>
</Tooltip.Root>

表示・非表示の切り替えはCSSだけで行っています。JavaScriptが担当するのはEscで閉じる処理だけです。

  • <Tooltip.Root>のホバー、または<Tooltip.Trigger>のキーボードフォーカス(:focus-visible)で表示されます。
  • ポップアップの上にポインタを移動しても消えません。
  • Escを押すと消えます。いったんポインタやフォーカスを外してから戻すと、再び表示されます。(Escで閉じた状態は<Tooltip.Root>data-dismissed属性で管理しています。)

<Tooltip.Root>の中に<Tooltip.Trigger><Tooltip.Popup>を並べて置くと、aria-describedbyidが自動で紐付きます。

  • <Tooltip.Popup>は、<Tooltip.Trigger>より後ろの兄弟要素として配置してください。フォーカス時の表示を兄弟セレクタで制御しているため、順番が逆になると、フォーカス時に表示されなくなります。
  • ポップアップの中にリンクやボタンなどの操作要素を置かないでください。操作要素を含めたい場合はPopoverを使用してください。
  • タッチデバイスではホバーが発生しないため、ポップアップが表示されないことがあります。重要な情報をポップアップの中だけに置かないでください。
  • <Tooltip.Trigger>はフォーカス可能な要素である必要があります。初期値のbutton以外(as="span"など)にする場合は、tabindex="0"を指定してください。

Styles

Tooltip のベーススタイルは、以下のCSSで定義されています。

_style.css
/*
 * 表示は CSS(:hover / :focus-visible)、配置は CSS Anchor Positioning。
 * anchor 配置は @supports で囲い、非対応(部分対応の Chrome 128〜130 含む)はルート基準の absolute 配置に落とす。
 */
@layer lism-block {
  /* 調整用の変数は Root で受ける(Root の props がインラインに書く --tooltip-* をここで拾う) */
  .b--tooltip {
    --duration: var(--tooltip-duration, 0.15s);
    --delay: var(--tooltip-delay, 0.4s); /* 表示までの待ち */
    --delay--close: var(--tooltip-delay--close, 0.15s); /* 退場猶予。トリガーからポップアップへポインタを移す間、表示を保つ */
    --offset: var(--tooltip-offset, var(--s5));
    --arrow-sz: var(--tooltip-arrow, 4px); /* 矢印の高さ。0 で矢印なし */
    --popup-gap: calc(var(--offset) + var(--arrow-sz)); /* popup 本体とトリガーの間隔。offset は矢印の先とトリガーの間隔 */
    display: inline-block;
    anchor-scope: --tooltip, --tooltip-popup;
  }
  .b--tooltip_trigger {
    anchor-name: --tooltip;
  }

  .b--tooltip_popup {
    --lh: 1.25;
    z-index: 10;
    inline-size: max-content;
    max-inline-size: min(20rem, 90vw);
    padding: 0.375em 0.625em;
    border-radius: var(--bdrs--10);
    background-color: var(--text);
    color: var(--base);
    font-size: var(--fz--s);
    visibility: hidden; /* pointer-events は使わない。transition しないため退場猶予中の当たり判定が消え、橋渡しが効かなくなる */
    opacity: 0;
    transition: var(--duration) var(--delay--close);
    transition-property: opacity, visibility;
  }

  /* 表示: ルートのホバー、またはトリガーのキーボードフォーカス(Popup は同じ Root 直下に置く前提) */
  .b--tooltip:hover > .b--tooltip_popup,
  .b--tooltip_trigger:focus-visible ~ .b--tooltip_popup {
    visibility: visible;
    opacity: 1;
    transition-delay: var(--delay);
  }

  /* Esc で閉じた状態(setTooltip.ts が付与)。同じ詳細度なので表示ルールより後に置いて勝たせる */
  .b--tooltip[data-dismissed] > .b--tooltip_popup {
    visibility: hidden;
    opacity: 0;
    transition: none;
  }

  /*
   * anchor 配置。position-area は側(--_side)と span(--_span)を var() で合成する。
   * align の start / end は span-* で片側に伸ばし place-self で端に揃える。span と揃える軸は side の系統ごとに違うので、
   * 系統側で --_span* / --_place* の候補を定義し、align 側はそれを選ぶだけにする。
   * top / bottom の align に span-x-* を使うのは RTL でも書字方向の端に揃えるため。物理と論理キーワードの混在は無効になるので混ぜない。
   */
  @supports (anchor-name: --a) and (anchor-scope: all) and (position-area: inline-start span-block-end) and (position-try-fallbacks: flip-block) {
    .b--tooltip_popup {
      --_span: span-all;
      position: fixed;
      anchor-name: --tooltip-popup; /* 矢印(::after)が popup の矩形を参照するため */
      position-anchor: --tooltip;
      position-area: var(--_side) var(--_span);
      /*
       * 非表示は display: none。visibility だけだと非表示中も配置され、スクロールで画面外に出た時点の flip が
       * 仕様(last successful position option)で記憶され、戻っても反転したまま残る。
       * 退場中は display を transition で残す(Firefox は display の transition 未対応で退場フェードが無くなるだけ)。
       */
      display: none;
      transition-property: opacity, visibility, display;
      transition-behavior: normal, normal, allow-discrete;
    }
    .b--tooltip:hover > .b--tooltip_popup,
    .b--tooltip_trigger:focus-visible ~ .b--tooltip_popup {
      display: block;
    }
    /* 入場の開始値。visibility を hidden から始め、入場ディレイ中は当たり判定を持たせない */
    @starting-style {
      .b--tooltip:hover > .b--tooltip_popup,
      .b--tooltip_trigger:focus-visible ~ .b--tooltip_popup {
        visibility: hidden;
        opacity: 0;
      }
    }
    .b--tooltip[data-dismissed] > .b--tooltip_popup {
      display: none;
    }
    /* トリガーとの隙間(offset)を透明な当たり判定で埋め、移動中に Root の :hover が外れないようにする。flip でどちら側に出ても効くよう全方向へ広げる */
    .b--tooltip_popup::before {
      content: '';
      position: absolute;
      inset: calc(-1 * var(--popup-gap));
      z-index: -1; /* 中身の上には被せない */
    }
    /*
     * ふきだしの矢印。ひし形の中心を「トリガー中心を popup の矩形にクランプした点」に置く。
     * flip 後の位置は CSS から知れない(@position-try でカスタムプロパティは設定できない)ので、
     * side / align / flip を見ずにトリガー側の辺へ寄るこの形にしている。
     * absolute だと popup が包含ブロックになりトリガーを anchor に取れないため fixed。
     * スクロール追従は既定アンカー(position-anchor)にしか効かないので、トリガーを既定アンカーにして省略形の anchor() で参照する。
     */
    .b--tooltip_popup::after {
      content: '';
      position: fixed;
      position-anchor: --tooltip;
      z-index: -1; /* 中身の下、popup の背景の上。内側半分は同色の背景に溶ける */
      inline-size: calc(var(--arrow-sz) * 2);
      block-size: calc(var(--arrow-sz) * 2);
      background-color: inherit;
      clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
      top: clamp(anchor(--tooltip-popup top), anchor(center), anchor(--tooltip-popup bottom));
      left: clamp(anchor(--tooltip-popup left), anchor(center), anchor(--tooltip-popup right));
      translate: -50% -50%;
    }
    /* --_place*: place-self の値(align-self justify-self)。揃える軸だけ start / end にする */
    .b--tooltip_popup:where([data-side='top'], [data-side='bottom']) {
      --_spanStart: span-x-end;
      --_spanEnd: span-x-start;
      --_placeStart: auto start;
      --_placeEnd: auto end;
      margin-block: var(--popup-gap);
      position-try-fallbacks:
        flip-block,
        flip-inline,
        flip-block flip-inline;
    }
    .b--tooltip_popup:where([data-side='start'], [data-side='end']) {
      --_spanStart: span-block-end;
      --_spanEnd: span-block-start;
      --_placeStart: start auto;
      --_placeEnd: end auto;
      margin-inline: var(--popup-gap);
      position-try-fallbacks:
        flip-inline,
        flip-block,
        flip-inline flip-block;
    }
    .b--tooltip_popup[data-side='top'] {
      --_side: top;
    }
    .b--tooltip_popup[data-side='bottom'] {
      --_side: bottom;
    }
    .b--tooltip_popup[data-side='start'] {
      --_side: inline-start;
    }
    .b--tooltip_popup[data-side='end'] {
      --_side: inline-end;
    }

    /* align: side 系統が用意した候補から選ぶ */
    .b--tooltip_popup:where([data-align='start']) {
      --_span: var(--_spanStart);
      place-self: var(--_placeStart);
    }
    .b--tooltip_popup:where([data-align='end']) {
      --_span: var(--_spanEnd);
      place-self: var(--_placeEnd);
    }
  }

  /* anchor 非対応: ルート基準の absolute 配置。side 軸と align 軸を別ルールにして打ち消しを不要にする */
  @supports not (
    (anchor-name: --a) and (anchor-scope: all) and (position-area: inline-start span-block-end) and (position-try-fallbacks: flip-block)
  ) {
    .b--tooltip {
      position: relative;
    }
    .b--tooltip_popup {
      position: absolute;
    }
    .b--tooltip_popup[data-side='top'] {
      bottom: 100%;
    }
    .b--tooltip_popup[data-side='bottom'] {
      top: 100%;
    }
    .b--tooltip_popup[data-side='start'] {
      inset-inline-end: 100%;
    }
    .b--tooltip_popup[data-side='end'] {
      inset-inline-start: 100%;
    }

    /* align: side が top / bottom なら横方向(inset-inline-* で書字方向に追従)、start / end なら縦方向の揃え */
    .b--tooltip_popup:is([data-side='top'], [data-side='bottom'])[data-align='center'] {
      left: 50%;
      translate: -50% 0;
    }
    .b--tooltip_popup:is([data-side='top'], [data-side='bottom'])[data-align='start'] {
      inset-inline-start: 0;
    }
    .b--tooltip_popup:is([data-side='top'], [data-side='bottom'])[data-align='end'] {
      inset-inline-end: 0;
    }
    .b--tooltip_popup:is([data-side='start'], [data-side='end'])[data-align='center'] {
      top: 50%;
      translate: 0 -50%;
    }
    .b--tooltip_popup:is([data-side='start'], [data-side='end'])[data-align='start'] {
      top: 0;
    }
    .b--tooltip_popup:is([data-side='start'], [data-side='end'])[data-align='end'] {
      bottom: 0;
    }
  }

  /* 減速設定時はフェードを無効化(ディレイは残す)。インラインの --duration 指定よりも優先させるため !important */
  @media (prefers-reduced-motion: reduce) {
    .b--tooltip {
      --duration: 0s !important;
    }
  }
}

ソースコード全体は GitHub で公開しています。

How to use

@lism-css/ui パッケージでTooltipとして提供しています。

Import

import { Tooltip } from '@lism-css/ui/react';

<Tooltip.Root>, <Tooltip.Trigger>, <Tooltip.Popup>が利用できます。

Props

プロパティ 説明
<Tooltip.Root>
tooltipId
<Tooltip.Trigger>aria-describedby<Tooltip.Popup>idに使われるIDを指定します。未指定の場合は自動生成されたIDが使われます。
<Tooltip.Root>
delay
ポップアップが表示されるまでの待ち時間を指定します。インラインスタイルの--tooltip-delayとして出力されます。
<Tooltip.Root>
offset
トリガーとポップアップの間隔を指定します。インラインスタイルの--tooltip-offsetとして出力されます。
<Tooltip.Trigger>
tooltipId
aria-describedbyの値として出力されます。<Tooltip.Root>の外で単体利用するときにだけ指定します。
<Tooltip.Popup>
id
id属性として出力されます。<Tooltip.Root>の外で単体利用するときにだけ指定します。
<Tooltip.Popup>
side
ポップアップを表示する向きを指定します。data-side属性として出力されます。指定できる値はtop / bottom / start / endで、初期値はtopです。start / endは横方向の向きで、書字方向に追従します(dir="ltr"ではstartが左、endが右です)。
<Tooltip.Popup>
align
ポップアップの揃え位置を指定します。sidetop / bottomのときは横方向、それ以外のときは縦方向の揃えです。data-align属性として出力されます。指定できる値はstart / center / endで、初期値はcenterです。

<Tooltip.Root>の配下では、<Tooltip.Trigger><Tooltip.Popup>に個別のIDを指定しないでください。IDを明示したい場合は<Tooltip.Root>tooltipIdだけを使います。

子要素側のtooltipId / idは、<Tooltip.Root>を使わずにパーツを離れた場所へ配置する(各パーツに同じIDを手動で渡す)ための手段です。<Tooltip.Root>の配下で一部の子にだけ指定すると、紐付けが壊れてしまいます。

CSS変数

見た目の調整には、以下のCSS変数が使えます。いずれも<Tooltip.Root>.b--tooltip)で受け取るため、<Tooltip.Root>またはその祖先要素に指定してください。<Tooltip.Popup>に指定しても効きません。

変数 初期値 説明
--tooltip-offset var(--s5) トリガーとポップアップの間隔。
--tooltip-delay 0.4s 表示されるまでの待ち時間。
--tooltip-delay--close 0.15s 非表示になるまでの猶予時間。トリガーからポップアップへポインタを移動する間、表示を保つために使われます。
--tooltip-duration 0.15s フェードにかかる時間。prefers-reduced-motion: reduceの環境では0sになります。

配色・余白・角丸・影は、Lismのプロパティ(bgc, c, p, bdrs, bxshなど)で上書きしてください。初期スタイルは--textを背景色、--baseを文字色にした反転配色です。

Examples

表示する向きを変える

<Tooltip.Popup>sideで、ポップアップを表示する向きを指定できます。

sideの指定例
side=“top” side=“bottom” side=“start” side=“end”
<Cluster g="15">
<Tooltip.Root tooltipId="tt-02">
<Tooltip.Trigger className="-bd -px:15 -py:5 -bdrs:10">top</Tooltip.Trigger>
<Tooltip.Popup side="top">side="top"</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root tooltipId="tt-03">
<Tooltip.Trigger className="-bd -px:15 -py:5 -bdrs:10">bottom</Tooltip.Trigger>
<Tooltip.Popup side="bottom">side="bottom"</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root tooltipId="tt-04">
<Tooltip.Trigger className="-bd -px:15 -py:5 -bdrs:10">start</Tooltip.Trigger>
<Tooltip.Popup side="start">side="start"</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root tooltipId="tt-05">
<Tooltip.Trigger className="-bd -px:15 -py:5 -bdrs:10">end</Tooltip.Trigger>
<Tooltip.Popup side="end">side="end"</Tooltip.Popup>
</Tooltip.Root>
</Cluster>

start / endは書字方向に追従する論理的な向きです。dir="ltr"ではstartが左、endが右になり、dir="rtl"の環境では左右が入れ替わります。

また、いずれの向きを指定した場合でも、画面の端に収まらないときは自動的に反対側へ反転します。

揃え位置を変える

<Tooltip.Popup>alignで、ポップアップの揃え位置を指定できます。sidetop / bottomのときは横方向、start / endのときは縦方向の揃えになります。

alignの指定例
side=“bottom” align=“start” side=“bottom” align=“end” side=“end” align=“start”
<Cluster g="15">
<Tooltip.Root tooltipId="tt-11">
<Tooltip.Trigger className="-bd -px:15 -py:5 -bdrs:10">bottom / start</Tooltip.Trigger>
<Tooltip.Popup side="bottom" align="start">side="bottom" align="start"</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root tooltipId="tt-12">
<Tooltip.Trigger className="-bd -px:15 -py:5 -bdrs:10">bottom / end</Tooltip.Trigger>
<Tooltip.Popup side="bottom" align="end">side="bottom" align="end"</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root tooltipId="tt-13">
<Tooltip.Trigger className="-bd -px:15 -py:5 -bdrs:10">end / start</Tooltip.Trigger>
<Tooltip.Popup side="end" align="start">side="end" align="start"</Tooltip.Popup>
</Tooltip.Root>
</Cluster>

alignstart / endは、sidetop / bottomのときは書字方向に追従し、dir="rtl"の環境では左右が入れ替わります。sidestart / endのときはstartが上、endが下です。

待ち時間と間隔を変える

<Tooltip.Root>delayで表示までの待ち時間を、offsetでトリガーとの間隔を調整できます。

delayoffsetの指定例
delay=“0s” delay=“1s” / offset=“1rem”
<Cluster g="15">
<Tooltip.Root tooltipId="tt-08" delay="0s">
<Tooltip.Trigger className="-bd -px:15 -py:5 -bdrs:10">すぐ表示</Tooltip.Trigger>
<Tooltip.Popup>delay="0s"</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root tooltipId="tt-09" delay="1s" offset="1rem">
<Tooltip.Trigger className="-bd -px:15 -py:5 -bdrs:10">1秒後に表示</Tooltip.Trigger>
<Tooltip.Popup>delay="1s" / offset="1rem"</Tooltip.Popup>
</Tooltip.Root>
</Cluster>

配色を変える

<Tooltip.Popup>にLismのプロパティを指定すると、配色や余白を変更できます。

配色の変更例
背景・文字色・余白を上書きしています。
<Tooltip.Root tooltipId="tt-10">
<Tooltip.Trigger className="-bd -px:15 -py:5 -bdrs:10">配色を変更したツールチップ</Tooltip.Trigger>
<Tooltip.Popup bgc="base-2" c="text" bd p="15" bdrs="10" bxsh="20">背景・文字色・余白を上書きしています。</Tooltip.Popup>
</Tooltip.Root>

ブラウザ対応

ポップアップの配置には CSS Anchor Positioning を使用しています。

未対応のブラウザ(Firefox ESR 140 など)では、<Tooltip.Root>(=トリガーを内包する要素)を基準にした絶対配置にフォールバックします。この場合、画面の端に収まらないときの自動反転は行われず、ポップアップが見切れることがあります。また、offset--tooltip-offset)による間隔も適用されません。

Firefox はdisplayプロパティのトランジションに対応していないため、Anchor Positioning が使える Firefox 147 以降では、非表示になるときのフェードと--tooltip-delay--closeの猶予が効かず、ポインタが外れると即座に消えます。表示されるときのフェードと待ち時間は効きます。

© 2026 Lism CSS.