Webサイトのモーダルは現在はHTMLコード5行で書ける

HTMLコード5行でモーダル作成

HTML5 <dialog> を活用すれば、JavaScriptの複雑な状態管理や外部ライブラリを一切使わずに、わずか5行のHTMLコードだけでモーダルを実装できます。

HTML
<button onclick="myModal.showModal()">モーダルを開く</button>
<dialog id="myModal" closedby="any">
  <p>モーダルのコンテンツです。</p>
  <button onclick="myModal.close()">閉じる</button>
</dialog>

モーダルのコンテンツです。

5行で完結する理由と解説

現在のブラウザは .showModal() を呼び出すだけで最前面のレイヤーに描画されます。

closedby="any" 属性を指定すると、Esc キーの押下だけでなく、モーダルの外側をクリックした際にも自動で閉じる動作を実装できます。

※ closedbyはSafariではまだ使えませんが、今後実装される予定です。

背景のスクロール防止もCSSで実装する

モーダルは前述のコードで実装できますが、モーダル表示中は背景をスクロールを防止する制御は含まれていないため、body:has(dialog[open]) に overflow: hidden; を適用してスクロール制御するのが望ましいです。

また、モーダルの外側(backdrop)の透過色がデフォルトだと薄めなので、::backdrop { background: rgba(0, 0, 0, 0.5); } で色を一般的なモーダルと同程度の暗さにしたほうが良いです。

CSS
body:has(dialog[open]) {
  overflow: hidden;
}

::backdrop {
  background: rgba(0, 0, 0, 0.5);
}

Vue 3 / Nuxt 4 で実装する方法

Vue 3 / Nuxt 4 で実装する際はテキスト部分にrefを設定して変更できるようにすると、汎用的に使えます。

Vue
<script setup lang="ts">
import { ref, useTemplateRef } from 'vue'

const dialogRef = useTemplateRef<HTMLDialogElement>('myModal')
const modalContent = ref('')

const openModal = (text: string) => {
  modalContent.value = text
  dialogRef.value?.showModal()
}

const closeModal = () => {
  dialogRef.value?.close()
}
</script>

<template>
  <div>
    <button type="button" @click="openModal('お知らせの詳細コンテンツです。')">
      モーダル1を開く
    </button>
    <button type="button" @click="openModal('利用規約の本文テキストです。')">
      モーダル2を開く
    </button>

    <dialog ref="myModal" closedby="any">
      <p>{{ modalContent }}</p>
      <button type="button" @click="closeModal">
        閉じる
      </button>
    </dialog>
  </div>
</template>

<style scoped>
:global(body:has(dialog[open])) {
  overflow: hidden;
}

dialog::backdrop {
  background: rgba(0, 0, 0, 0.5);
}
</style>

React / Next.js で実装する方法

React / Next.js(App Router)にて <dialog> 要素を実装する場合も、useRefを使用してDOM要素を参照し、.showModal() や .close() を呼び出します。

表示テキストを動的に変更する際は useState で状態を保持します。

React / Next.js
'use client'

import { useRef, useState } from 'react'

export default function ModalExample() {
  const dialogRef = useRef<HTMLDialogElement>(null)
  const [modalContent, setModalContent] = useState('')

  const openModal = (text: string) => {
    setModalContent(text)
    dialogRef.current?.showModal()
  }

  const closeModal = () => {
    dialogRef.current?.close()
  }

  return (
    <div>
      <button type="button" onClick={() => openModal('お知らせの詳細コンテンツです。')}>
        モーダル1を開く
      </button>
      <button type="button" onClick={() => openModal('利用規約の本文テキストです。')}>
        モーダル2を開く
      </button>

      <dialog ref={dialogRef} closedby="any">
        <p>{modalContent}</p>
        <button type="button" onClick={closeModal}>
          閉じる
        </button>
      </dialog>

      <style jsx global>{`
        body:has(dialog[open]) {
          overflow: hidden;
        }

        dialog::backdrop {
          background: rgba(0, 0, 0, 0.5);
        }
      `}</style>
    </div>
  )
}

alert()よりもdialogのモーダルのほうが良い

JavaScriptでメッセージを表示する際に、手軽に使えるalert()ですが、WebサイトのUIとして使用するにはいくつかの問題があります。

例えば、JavaScriptの処理がブロックされる、デザインを自由に変更できない、といった点です。

このようなデメリットがあるため、JavaScriptのalert()ではなくHTMLの<dialog>要素を使用することをオススメします。