SCALE — Build Lab
UI部品 · REACT COMPONENT

ツールチップ

CATEGORYUI部品 TYPEReact Component EFFORT60〜120分 DIFFICULTY
PRIMARY CODE
tsx
'use client';
import { useState, useRef, ReactNode, ReactElement, cloneElement } from 'react';

export function Tooltip({ children, content, side = 'top' }: {
  children: ReactElement;
  content: ReactNode;
  side?: 'top' | 'bottom' | 'left' | 'right';
}) {
  const [show, setShow] = useState(false);
  const ref = useRef<HTMLSpanElement>(null);

  const positions: Record<string, any> = {
    top:    { bottom: '100%', left: '50%', transform: 'translateX(-50%) translateY(-6px)' },
    bottom: { top: '100%',    left: '50%', transform: 'translateX(-50%) translateY(6px)' },
    left:   { right: '100%',  top: '50%',  transform: 'translateY(-50%) translateX(-6px)' },
    right:  { left: '100%',   top: '50%',  transform: 'translateY(-50%) translateX(6px)' },
  };

  return (
    <span ref={ref} style={{ position: 'relative', display: 'inline-block' }}
      onMouseEnter={() => setShow(true)} onMouseLeave={() => setShow(false)}
      onFocus={() => setShow(true)}     onBlur={() => setShow(false)}>
      {children}
      {show && (
        <span role="tooltip" style={{
          position: 'absolute', ...positions[side],
          background: 'rgba(8,8,10,.94)', border: '1px solid rgba(255,255,255,.07)',
          color: '#fff', fontSize: '.7rem', padding: '.4rem .65rem', borderRadius: 4,
          whiteSpace: 'nowrap', zIndex: 50, pointerEvents: 'none',
        }}>
          {content}
        </span>
      )}
    </span>
  );
}
前提条件
Tailwind CSS v4TypeScript 5
USE CASES
  • 任意のダッシュボードに組み込み

ツールチップ

:LiTarget: 用途

ホバー/フォーカスでツールチップ表示。位置自動調整(top/bottom/left/right)。

:LiSparkle: 特徴

  • ホバー表示
  • フォーカス表示
  • 位置自動調整
  • a11y 対応

:LiCode: コード(コピペ用)

'use client';
import { useState, useRef, ReactNode, ReactElement, cloneElement } from 'react';

export function Tooltip({ children, content, side = 'top' }: {
  children: ReactElement;
  content: ReactNode;
  side?: 'top' | 'bottom' | 'left' | 'right';
}) {
  const [show, setShow] = useState(false);
  const ref = useRef<HTMLSpanElement>(null);

  const positions: Record<string, any> = {
    top:    { bottom: '100%', left: '50%', transform: 'translateX(-50%) translateY(-6px)' },
    bottom: { top: '100%',    left: '50%', transform: 'translateX(-50%) translateY(6px)' },
    left:   { right: '100%',  top: '50%',  transform: 'translateY(-50%) translateX(-6px)' },
    right:  { left: '100%',   top: '50%',  transform: 'translateY(-50%) translateX(6px)' },
  };

  return (
    <span ref={ref} style={{ position: 'relative', display: 'inline-block' }}
      onMouseEnter={() => setShow(true)} onMouseLeave={() => setShow(false)}
      onFocus={() => setShow(true)}     onBlur={() => setShow(false)}>
      {children}
      {show && (
        <span role="tooltip" style={{
          position: 'absolute', ...positions[side],
          background: 'rgba(8,8,10,.94)', border: '1px solid rgba(255,255,255,.07)',
          color: '#fff', fontSize: '.7rem', padding: '.4rem .65rem', borderRadius: 4,
          whiteSpace: 'nowrap', zIndex: 50, pointerEvents: 'none',
        }}>
          {content}
        </span>
      )}
    </span>
  );
}

:LiHandPointer: 使い方

対象プロジェクトに該当ファイルをコピーして、props を流し込むだけ。

:LiAlertCircle: 注意事項

  • 依存パッケージを忘れず追加