世界を動かす技術を、日本語で。

コンテンツテンプレート要素

概要

  • <template>要素 の属性・使い方・注意点の整理
  • Declarative Shadow DOM の概要と属性説明
  • DocumentFragment 利用時の注意点
  • 具体的なHTML・JavaScript例 による実装方法
  • 関連仕様・アクセシビリティ・参考情報 の紹介

<template>要素の属性と使い方

  • グローバル属性 に加え、<template>要素は特有の属性を持つ
    • shadowrootmode :親要素にシャドウルートを宣言的に作成
      • open :内部シャドウDOMをJavaScriptから参照可能(推奨)
      • closed :内部シャドウDOMをJavaScriptから隠蔽
    • shadowrootclonable :Node.cloneNode()やDocument.importNode()でシャドウルートごと複製可能に設定
    • shadowrootdelegatesfocus :非フォーカス要素選択時、最初のフォーカス可能要素へフォーカスを委譲
    • shadowrootserializable(実験的) :ShadowRootをシリアライズ可能に設定
  • shadowroot属性 は非推奨。Chrome 90-110で一時的にサポートされていたが、現在は shadowrootmode に統一

<template>要素の利用上の注意

  • <template>要素自体には実体の子要素は存在しない
    • Node.childNodes は常に空
    • contentプロパティ 経由でのみ内部DOMにアクセス
  • appendChild()等で直接子要素を追加 すると、contentプロパティのDocumentFragmentには反映されない
  • <html><head><body>タグは構文エラー として無視される
  • 許可される親要素 :メタデータ・フロー・フレージング・スクリプトサポート要素を許容する全ての要素

<template>の主な用途

  • テンプレート文書フラグメント

    • contentプロパティ は読み取り専用のDocumentFragmentを返却
    • cloneNode() で複製し、任意の場所に挿入可能
    • DocumentFragment の落とし穴:イベントハンドラはフラグメント自体に付与しても移動されない
      • 子要素に直接イベントハンドラをつけることが推奨
  • Declarative Shadow DOM

    • shadowrootmode 属性(open/closed)指定時、HTMLパーサが即座にShadowRootを生成
    • 親要素にShadowRootがアタッチされ、<template>の内容がその内部DOMとなる
    • 複数のdeclarative shadow root が存在する場合、最初の1つのみShadowRoot化。以降は通常のHTMLTemplateElementとして扱われる

コード例

  • テーブル行生成例

    • <table>と<template>要素を用意
    • JavaScriptでテンプレートを複製し、tbodyへ挿入
    • テンプレート未対応ブラウザでは代替処理
  • Declarative Shadow DOM実装例

    • グローバルstyleShadowRoot内style の挙動比較
    • shadowrootmode 対応確認し、非対応時に警告表示
    • ShadowRoot内のp要素に異なるスタイル適用
  • shadowrootdelegatesfocus利用例

    • shadowrootdelegatesfocus 属性あり・なしの比較
    • テキスト(非フォーカス要素)クリック時のフォーカス挙動の違い
  • DocumentFragmentのイベント伝播例

    • Fragment自身にイベント付与 → 移動時にハンドラ消失
    • 子要素にイベント付与 → 正常にイベント発火

技術仕様と互換性

  • 内容カテゴリ :メタデータ・フロー・フレージング・スクリプトサポート
  • 許可内容 :なし(内部DOMはcontentプロパティ経由でのみ取得)
  • タグ省略不可 :開始・終了タグ必須
  • ARIAロール :なし
  • DOMインターフェース :HTMLTemplateElement
  • ブラウザ互換性 :主要ブラウザで対応(詳細はMDN等参照)

関連情報・参考リンク

  • part/exportparts属性
  • <slot>要素
  • :has-slotted, :host, :host(), :host-context()疑似クラス
  • ::part, ::slotted疑似要素
  • ShadowRootインターフェース
  • テンプレート・スロット利用ガイド
  • CSSスコープモジュール
  • Declarative Shadow DOM解説(web.dev, MDN等)

Hackerたちの意見

テンプレート要素って、ページ内にJSONデータを保存してJSで使うのにいい場所なのかな?

application/jsonタイプのスクリプト要素と同じくらい良いよね。

この使い方にはデータ属性がいいかもね。https://developer.mozilla.org/en-US/docs/Web/HTML/How_to/Use... ただ、考えてるJSONデータの形によるけど。

自分のポートフォリオ(miler.codeberg.page)とそのリファクタリング&デザイン変更を進めてるんだけど(早く終わらせたい!)、データをjsonから取得してそれをデータベースみたいに扱ってるんだ。ノードをクエリしてデータを設定するプロセスは、プレーンJSではまだ少し原始的だと感じるし、大きなデータセットやたくさんのノードには厄介だと思うけど、少なくとも現代のJSではXMLHTTPRequestとかでjsonを取得する必要はなくて、普通のJSモジュールみたいにインポートできるから、そこは良い点だね。

いや、documentはその内容をテキストとして解析するけど、DOMとして解析するんだ。つまり、``をエスケープする必要がないってこと。自分と何人かのブラウザエンジニアが、他のモジュールに通常のインポート文でインポートできるインラインモジュール(JSONを含む)を許可する提案に取り組んでるんだ。だから、"-json"タイプをおすすめしてるんだよ。将来的なネイティブの"json"タイプと衝突しないようにね。

最初の例を読んでみたけど、これから得られるメリットがあんまり見えないな。テンプレートを使ってテーブル行を作るのも、ゼロから作るのと同じくらい手間がかかるし。

これはウェブコンポーネント向けに設計されてるんじゃないかな。

この例は、DOMノードに簡単にクローンできるっていうポイントを示すための、ちょっと作り込まれたシンプルな例なんだ。もっと分かりやすい例としては、ここで示されている、との組み合わせでの使い方があるよ: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/...

もちろん、この例の目的は論理的に意味のある例を使って教えることだよ。テンプレートは無限に大きくできるし、テンプレートが大きくなるにつれて、手動でのdocument.createElement呼び出しとの違いはもっと大きくなるからね。

そうだね、これってJSでテンプレートを定義するよりも複雑に見える。結局、JSを使ってそれらとやり取りしなきゃいけないし、要はよく知られた文字列とDOMノードを作るためのファクトリーとのマッピングみたいなもんだよね。柔軟性がないのが残念。利点は、JSでテンプレートに触れずにHTML内でインラインで使うときだけみたいだね。

クラスや構造のような多くの属性を持つ大きなテンプレートを考えてみて。これを何度もレンダリングしたいときは、構造やクラスを何度も再作成するのではなく、ユニークなインスタンスのためにいくつかのことを更新するだけで済むよ。

ReactやVueのハローワールドの意味が全然わからない。HTMLを書くよりも冗長だし。

Timelinize[0]でこのタグをかなり使ってるよ。バニラJSで書いてる結構洗練されたUIなんだ。jQueryすら使ってないし。地図や日付時刻、Bootstrap+Tablerのスタイル用にいくつかライブラリは使ってるけど、それだけ。ボイラープレートのJSは書いたけど、フロントエンドを完全にコントロールできてるし、どう動いてるかも理解してるから、コンパイルする必要もないんだ。とにかく、今のところうまくいってるよ!このタグはコンポーネントをレイアウトして、シンプルなJS関数で埋め込むのに最適だね。コードコメントに書いたニュアンスがあるんだけど、// おおおお、テンプレートタグの内容をクローンする時はfirstElementChildを使わないとダメだよ(!!!!):// https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/template#avoiding_documentfragment_pitfalls // これに時間かけすぎたわ。const elem = $(tplSelector); if (!elem) return; return elem.content.firstElementChild.cloneNode(true); これに気づいてからは、スムーズに進んでてすごくパワフルだよ。でも、実験してるだけだったから、まだコードを全然整理してないから、ちょっとスパゲッティ状態だよ :) 探す時は、散らかってても気にしないでね。 --- [0]: https://github.com/timelinize/timelinize - 自分のコンピュータでデータをまとめて、1つのタイムラインに整理するための自己ホスト型アプリケーション。SNSアカウント、写真ライブラリ、テキストメッセージ、GPSトラック、ブラウジング履歴など。もうすぐデビュー準備完了...

最初はActivityPubのフロントエンドを作るためにバニラテンプレートを考えたけど、結局lit-jsがウェブコンポーネントに追加するスイートさがファイルサイズのオーバーヘッドに見合うと思ったんだ。

テンプレートをクローンするには、document.importNode()を使うべきだよ。テンプレートの内容はメインドキュメントとは別のドキュメントにあるから、それが不活性にしてるんだ。importNode()はノードをドキュメントに取り込むから、すぐに正しいプロトタイプを持つようになる(カスタム要素も含まれる)。そうじゃないと、要素が最初にドキュメントに添付されたときに採用ステップが実行されて、挿入や追加の操作に余計な時間がかかることになる。だから、こうするべきだよ:document.importNode(elem.content, true); そうすれば、ノードを取り出せるDocumentFragmentができるよ。あるいは、全体のフラグメントを追加するだけでもいい。

Timelinize、すごいね。こんなのずっと欲しかった!アイデアが大好き!gpxサポートについてだけど…間違った方向に導きたくないけど、https://www.sethoscope.net/heatmap/を見たことある?

おお、クールなプロジェクトだね!数年前にこんなの作ろうか考えたことがあるけど、オープンソースでセルフホスティングじゃないと使う気にならないから(AGPLv3を使ってくれてありがとう!)。結局、分析麻痺に陥っちゃって、すごく感心してるよ :-)

これはほぼ間違いなく、望まれない監視に使われるだろうね。

Tablerって何?

いくつかのライブラリを使ってるよ(マッピングや日付時刻関連)。特にTemporal [0]がもっと広く使われるのを楽しみにしてる。これと最近のIntl関連のいくつかのおかげで、日付時刻関連のライブラリがほとんど必要なくなるところまで来てるよ。 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

最近、妻の写真ブログで商品を表示するためにテンプレート要素をよく使ってるんだ。商品データはヘッドレスのMedusaJSインスタンスから読み込まれて、ブログのテーマに合ったテンプレートに組み込まれて、ページに表示される感じ。ページの読み込みが妻の画像のサイズで結構重くなってるから、Reactみたいなのは使いたくなかったんだ。テンプレートはその点では使いやすかったよ。

正直、preactは4kBだから、どんな画像と比べても微々たるもんだよね。

タグの目的って何なの?ドキュメントフラグメントを作るためだけ?HTMLではそれしかできないと思うんだけど。JSを使うなら、どんな要素にもシャドウDOMを付けられるし。ウェブコンポーネント用のスロットがもう一つのポイントかな?

サーバーで通常のページロードでは処理されないフラグメントを生成させて、それをJavaScriptがテンプレートとして使って埋め込むってことをやったことがあるよ。これでSPAと比べて初期のページロードが速くなったし、読み込んだ後も同じくらいダイナミックだった。まあ、うまくいったけど、いくつか粗い部分があって、スタンダードプロセスがそこにもっと焦点を当ててくれたらよかったな。JSXみたいなのはすごく遅いしメモリも食うけど、便利だからみんな使ってるし、使いやすさを改善できたらいいな。

いい質問だね。確かに、HTML5の方向に行かなかったウェブコンポーネントの話があったし、template要素はその一部だよね。仕様に入った要素は簡単には削除できないから、時間が経つにつれて再利用されることが多いんだ。

ただドキュメントフラグメントを作るため? そうだと思うけど、実際に使ったことはまだないから、あんまり信用しないでね。テンプレートの外に表示されないHTMLタグでやるのと比べると、パフォーマンス面での利点(パーサーがそれをアクティブなDOMの一部として扱わないから)や便利さ(確か、内容はquerySelector/querySelectorAllに拾われないはず)もあるし。JSでドキュメントフラグメントを作るのと比べると、作成や編集がずっと楽だし、デザインとコードの結びつきも緩くなるから、JSに触れたくないデザイナーでもある程度は簡単にいじれるよ。他の方法で同じ分離を実現しようとすると、作業をデプロイする際に余分なビルドステップが必要になるかもしれないね。

Alpine.jsは、状態を持つデータを使ったif文やforループのためにタグを使うんだ。例えば、テンプレートを考えるとき、スロットやフラグメントよりもシンプルな考え方だよ。

テンプレート要素みたいな標準的なウェブ技術を活用して、現代のウェブサイトを作るための未開拓の可能性があると思う。多くの現代のウェブフレームワークが抱える肥大化した抽象化を避けながらね。要するに、昔のウェブ開発のシンプルさと現代のウェブ機能を組み合わせる必要があるんだ。最近のライブラリやフレームワークでこの方向に進んでいるものもいくつかあるよ:Nue、Datastar、Lit。これらのDXやUXは新鮮な空気を感じさせるね。自分でもMiuでこのアプローチを試してみたけど、まだウェブコンポーネントとうまく統合できてないけど、ほぼ完成してる。WCスタンダードには不満もあるけど、それは別の話。願わくば、現代のウェブ開発が初期の頃のように、サードパーティのライブラリやフレームワークを必要としなくなることだね。ウェブ開発者はネイティブウェブAPIの制限からこれに慣れちゃったけど、ウェブはその後大きく成長したから。現代のワークフローが標準になる前は、テキストエディタを開いて数分で動くウェブページが作れるのが魔法みたいだった。依存関係もビルドステップもなしで、無駄もなく。あの時代を取り戻したいな。

なぜDOMにネイティブのテンプレーティングを追加すべきかについて、いくつかブログ記事を書いたよ。今こそDOMテンプレーティングAPIが必要な時だと思う:https://justinfagnani.com/2025/06/26/the-time-is-right-for-a... ネイティブのDOMテンプレーティングAPIはどんなものになるべきか?: https://justinfagnani.com/2025/06/30/what-should-a-dom-templ...

そして、ほとんどの現代的なウェブフレームワークにありがちな肥大化した抽象化を避けることができる。要するに、昔のウェブ開発のシンプルさと現代のウェブ機能を組み合わせる必要があるんだ。フレームワークを実際に作っている人たち(Solid、Preact、Vue、Svelte)の話を聞けば、「現代のウェブ機能」には開発を楽にする実際の機能がほとんどないってことがわかるよ。すべてのフレームワークは、「肥大化した抽象化」を実際のブラウザ機能に置き換えられたら大喜びだろうね。残念ながら、その機能は非常に少数のChrome開発者以外が望むものとは完全に孤立して作られている。 > ウェブ開発者は、ネイティブウェブAPIの制限のためにこれに慣れちゃったけど、ウェブはそれ以来大きく成長したんだ。実際にはそうじゃないよ。少なくとも君が望む方向には。 > 依存関係なし、ビルドステップなし、無駄なし。これを戻してほしいな。今それをするのを止めるものは何もないよ。

テンプレートはしっかりしていて、ウェブコンポーネントのためにhtml-in-jsを含めるよりも少しパフォーマンスがいいけど、でも、HTML、JS、CSSを組み込んだウェブコンポーネント用のファイルフォーマットがあればいいのにって思う。ライブラリとして簡単に読み込んだり配布したりできる構造的な方法でね。ウェブコンポーネントを正しく使うためには、ロジックをインポートしてからHTMLの部分をインラインで書かなきゃいけないのが好きじゃないんだ。

HTMLモジュールに関する仕様の問題がオープンになってるよ[1]。いくつかの提案もあるし、このコンセプトを進めるためには、やる気のあるチャンピオンが必要だね。最近、マイクロソフトがこれに興味を示しているみたい。ユーザーランドの単一ファイルコンポーネントプロジェクトもあって、私のHeximal[2]プロジェクトもその一つ。ただ、Heximalは特にモジュールフォーマットには取り組んでなくて、HTMLモジュールのポリフィルが必要なんだ。でも、今の状況はそんなに悪くないと思うよ。コンポーネントの消費者にとって、コンテナフォーマットはほとんど関係ないはず。定義が.jsファイルでも.htmlファイルでも、コンポーネントをインポートする必要があるし、一度インポートすれば同じように使えるからね。ファイルフォーマットが重要になるケースはほとんどないと思う。もしかしたら、HTMLベースのウェブコンポーネントが安全と見なされる場合、JSがオフのときに使うためかもしれないけど(そのテンプレートが攻撃を許すほど表現力があるかもしれないから)。[1]: https://github.com/WICG/webcomponents/issues/645 [2]: https://heximal.dev/ (ごめん、モバイルだとサイトが崩れてる。直さなきゃ!)

そういえば、テンプレートにスタイルタグを追加するだけで、そこにスコープされるんだよね。これがローカルCSSルールを得る唯一の方法だよ。それに、スクリプトタグも好きなところに追加できるけど、DOMに追加されたときに実行されるけど、スコープはされないよ。

SalesforceにはLWCっていうすごくいいモジュールフォーマットがあったんだ。それも片方向バインディングをサポートしてた。残念ながら、顧客利用以外ではあまり推進されなかったけど。

Shopifyは、Storefront Web Componentsプロジェクトで要素を多く使ってるけど、テンプレート自体は子要素としてウェブコンポーネントに渡されて、必要なデータを動的に取得してからDOMに要素をマウントするんだよね。: https://shopify.dev/docs/api/storefront-web-components

特別なタグの意味って何? 結局、全部JSON、CSS、JavaScript、そして大量のDIVやSPAN要素じゃないの?

これが解決しようとしている問題は何で、実際にそれを解決できているの?スロットを埋めるためにJSがテンプレートの内部構造をモデル化する必要があるから、魅力が見えにくいんだよね。

シャドウDOMは、ウェブコンポーネントがメインDOMにスロットフィラーを持っている場合にスロットを自動で埋めることができるんだ。シャドウDOMを呼び出したり作成するためにはJSが必要だけど、その場合はJSが最小限で済むかもしれないし、内部構造をモデル化する必要もないかもね。でも、テンプレートが解決しようとしている大きな問題は、パースされているけど「生」ではないDOMフラグメントを作ることなんだ。テンプレートタグが登場する前は、JSやcreateElement/createElementNS以外に良い方法がなかったし、それは常にブラウザの最適化されたHTMLパーサーより遅かったんだ。また、スロットタグは、初めてのタグとしてデフォルトの(ブラウザCSS)動作がdisplay: contents;になるという小さな問題を解決しているんだ。CSSのワンライナーからの大きな変化ではないけど、テンプレート以外でも使える場面はまだあるよ。