SCALE — Build Lab
開発パターン · REACT PATTERN

モーダル積層管理

CATEGORY開発パターン TYPEReact Pattern EFFORT60〜180分 DIFFICULTY
PRIMARY CODE
tsx
'use client';
import { useEffect, useRef, ReactNode } from 'react';

let stackDepth = 0;

// z-index 自動管理 + Esc キー対応モーダル
export function Modal({ open, onClose, children }: { open: boolean; onClose: () => void; children: ReactNode }) {
  const z = useRef(0);

  useEffect(() => {
    if (!open) return;
    stackDepth++;
    z.current = 50 + stackDepth * 10;
    // 一番上のモーダルだけ Esc を効かせる
    const me = stackDepth;
    const onKey = (e: KeyboardEvent) => {
      if (e.key === 'Escape' && me === stackDepth) onClose();
    };
    document.addEventListener('keydown', onKey);
    // body スクロールロック(最初のモーダル時だけ)
    if (stackDepth === 1) document.body.style.overflow = 'hidden';
    return () => {
      stackDepth--;
      document.removeEventListener('keydown', onKey);
      if (stackDepth === 0) document.body.style.overflow = '';
    };
  }, [open, onClose]);

  if (!open) return null;
  return (
    <div className="fixed inset-0 bg-black/60 flex items-center justify-center p-4" style={{ zIndex: z.current }} onClick={onClose}>
      <div className="bg-zinc-900 border border-zinc-800 rounded-2xl p-6 max-w-2xl w-full" onClick={(e) => e.stopPropagation()}>
        {children}
      </div>
    </div>
  );
}
前提条件
Tailwind CSS v4TypeScript 5
USE CASES
  • 編集モーダル内の確認モーダル

モーダル積層管理

:LiTarget: 用途

複数モーダルを安全に積み重ねるパターン。z-indexとEscキー処理。

:LiSparkle: 特徴

  • z-index自動管理
  • Escキー処理
  • スクロールロック

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

'use client';
import { useEffect, useRef, ReactNode } from 'react';

let stackDepth = 0;

// z-index 自動管理 + Esc キー対応モーダル
export function Modal({ open, onClose, children }: { open: boolean; onClose: () => void; children: ReactNode }) {
  const z = useRef(0);

  useEffect(() => {
    if (!open) return;
    stackDepth++;
    z.current = 50 + stackDepth * 10;
    // 一番上のモーダルだけ Esc を効かせる
    const me = stackDepth;
    const onKey = (e: KeyboardEvent) => {
      if (e.key === 'Escape' && me === stackDepth) onClose();
    };
    document.addEventListener('keydown', onKey);
    // body スクロールロック(最初のモーダル時だけ)
    if (stackDepth === 1) document.body.style.overflow = 'hidden';
    return () => {
      stackDepth--;
      document.removeEventListener('keydown', onKey);
      if (stackDepth === 0) document.body.style.overflow = '';
    };
  }, [open, onClose]);

  if (!open) return null;
  return (
    <div className="fixed inset-0 bg-black/60 flex items-center justify-center p-4" style={{ zIndex: z.current }} onClick={onClose}>
      <div className="bg-zinc-900 border border-zinc-800 rounded-2xl p-6 max-w-2xl w-full" onClick={(e) => e.stopPropagation()}>
        {children}
      </div>
    </div>
  );
}

:LiHandPointer: 使い方

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

:LiAlertCircle: 注意事項

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