Litは、Web標準のWeb Componentsを少ないコードで作るためのJavaScriptライブラリです。テンプレートを描画するlit-htmlと、コンポーネントの基底クラスであるLitElementをlitという1つのパッケージにまとめて配布しており、公式サイトは本体の大きさを「約5KB(minified and compressed)」と公表しています。
この記事では、2026年5月14日公開の最新版3.3.3を前提に、lit-htmlとLitElementの関係、テンプレートの書き方、TypeScriptやCDNでの使い方、SSR、Reactとの組み合わせまでを、node v26.5.0で動作を確かめたコードで説明します。2025年10月にGoogle単独の管理からOpenJS Foundationへ移った経緯にも触れます。
まとめ:Litとlit-htmlの要点
- 構成:npmの
lit(3.3.3)はlit-html(3.3.3)・lit-element(4.2.2)・@lit/reactive-element(2.1.2)を束ねたパッケージ。新規に入れるのはlitだけでよい。 - lit-html:タグ付きテンプレートリテラル
html`...`でUIを書き、再描画では値の入る箇所だけを書き換える。仮想DOMの差分計算はしない。 - LitElement:リアクティブなプロパティとShadow DOM内のスコープ付きスタイルを持つカスタム要素の基底クラス。作った部品はReact・Vue・Angularやフレームワーク無しのHTMLでもそのまま使える。
- 運営:Polymer Projectとして始まり、長くGoogleが開発してきたが、2025年10月14日にOpenJS FoundationのImpact Projectへ移管された。ライセンスはBSD-3-Clause。
- 注意点:SSRは
@lit-labs/ssr(Labs扱い)で、非同期処理とShadow DOMを使わないコンポーネントには未対応。JavaScriptではリアクティブなプロパティをクラスフィールドで初期化してはいけない。
Litを構成する3つのパッケージと版の履歴
「lit-html」「LitElement」「Lit」の3つの名前が混在するのは、歴史的に別々のライブラリだったものを2021年のLit 2で1つに統合したためです。現在も各パッケージはnpmに個別に公開されており、litはそれらを依存として読み込む入り口にあたります。
lit-html・LitElement・litの役割分担
| パッケージ | 最新版 | 役割 |
|---|---|---|
lit |
3.3.3 | 下の3つをまとめて再公開する入り口 |
lit-html |
3.3.3 | テンプレートの記述と描画 |
lit-element |
4.2.2 | LitElement(描画付きの基底クラス) |
@lit/reactive-element |
2.1.2 | プロパティ変更で更新を予約する土台 |
版番号がそろっていない点に注意してください。litの3.3.3はlit-html ^3.3.0・lit-element ^4.2.0・@lit/reactive-element ^2.1.0に依存しており、「LitElement 4」と「Lit 3」は同じ世代を指します。古い記事のimport {LitElement} from 'lit-element'は今も動きますが、新規コードはfrom 'lit'に統一するのが公式の書き方です。lit-htmlは非推奨になっておらず、コンポーネントを作らずテンプレートだけ使いたい場合は単体で入れられます。
Polymerから始まった版の履歴とOpenJS Foundationへの移管
| 日付 | 出来事 |
|---|---|
| 2019-02-05 | lit-html 1.0とLitElement 2.0の安定版(Polymer Project) |
| 2021-09-21 | Lit 2.0。2つをlitパッケージへ統合 |
| 2023-10-10 | Lit 3.0。IE11サポートを終了 |
| 2025-10-14 | OpenJS FoundationのImpact Projectへ移管 |
| 2026-05-14 | Lit 3.3.3(執筆時点の最新) |
「Googleのフレームワーク」と紹介されることが多いものの、公式ブログの移管告知には、コード・ドキュメント・Webサイト・Litのブランドを含む全資産をOpenJS Foundationへ移し、特定企業に将来を依存しない体制にすると書かれています。発足時の技術運営委員会(TSC)はGoogle所属2名、Adobe・Reddit所属各1名、独立の開発者2名で構成されています。同じ告知では、利用例としてAdobeのPhotoshop・IllustratorのWeb版、Reddit、MDN、Chrome DevTools、FirefoxのブラウザUIが挙げられています。採用の判断で「Google次第で終わるのでは」という点を気にしていたなら、その前提は2025年10月で変わりました。
lit-htmlのテンプレート構文と差分更新の仕組み
html関数とrenderで動く最小のテンプレート
lit-htmlの中心は、テンプレートを作るhtmlタグ関数と、それをDOMへ書き出すrender関数の2つです。
import {html, render} from 'lit-html';
const greet = (name, n) => html`<p>こんにちは、${name}さん(${n}件)</p>`;
render(greet('Lit', 3), document.body);
render(greet('Lit', 4), document.body); // 2回目は「3」の部分だけが書き換わる
このテンプレートを@lit-labs/ssrで文字列に描画すると、次のように値が入る場所ごとにlit-partという目印のコメントが付きます。
<!--lit-part E+ntxMkYaY4=--><p>こんにちは、<!--lit-part-->Lit<!--/lit-part-->さん(<!--lit-part-->3<!--/lit-part-->件)</p><!--/lit-part-->
lit-htmlは初回描画で固定部分と可変部分を切り分けて覚えておき、2回目以降は可変部分の値だけを前回と比べます。公式サイトの表現では「UIの動的な部分にしか触れないため、仮想ツリーを作り直してDOMと差分を取る必要がない」仕組みで、Reactの仮想DOMとの一番大きな違いです。
バインディング記法5種類の使い分け
値の入れ先は、属性名の前に付ける記号で決まります。
| 書き方 | 入れ先 | 例 |
|---|---|---|
${...}(要素の中) |
子ノード | <p>${msg}</p> |
name=${...} |
属性(文字列) | class=${cls} |
?name=${...} |
真偽属性の付け外し | ?disabled=${busy} |
.name=${...} |
JavaScriptプロパティ | .value=${text} |
@name=${...} |
イベントリスナー | @click=${onClick} |
配列やオブジェクトを子コンポーネントへ渡すときは、属性ではなく.付きのプロパティで渡します。HTMLの属性は大文字小文字を区別しませんが、.listItems=${items}のようなプロパティ名は大文字小文字がそのまま保たれます。繰り返し・条件分岐・クラスの付け外しなどは、repeat・when・classMap・styleMap・描画後の要素を参照するrefといった組み込みディレクティブで書けます(lit/directives/ref.jsのように個別にimportします)。
LitElementでWeb Componentsを作る手順
npmでのLit導入とカスタム要素の登録
既存のnpmプロジェクトにはnpm i litで追加できます。以下のコードをcounter.jsとして保存し、Viteなどでベアモジュール指定子を解決できる環境から、HTMLのmoduleスクリプトとして読み込みます。次はクリックで数が増えるボタンを、デコレーターを使わない素のJavaScriptで書いた例です。
import {LitElement, html, css} from 'lit';
export class CounterButton extends LitElement {
static properties = {
count: {type: Number},
label: {type: String},
};
static styles = css`
button { font: inherit; padding: 4px 12px; }
`;
constructor() {
super();
this.count = 0; // 初期値はconstructorで入れる
this.label = '回';
}
render() {
return html`
<button @click=${() => this.count++}>
${this.count}${this.label}
</button>
`;
}
}
customElements.define('counter-button', CounterButton);
HTML側では<counter-button count="3"></counter-button>と書くだけで使えます。static propertiesに宣言したプロパティは値が変わると更新が非同期に予約され、同じタイミングで複数が変わっても描画は1回にまとめられます。type: Numberの指定により、属性の文字列「3」は数値の3に変換されます。
TypeScriptのデコレーターとaccessor
TypeScriptでは@customElement・@property・@state・@queryなどのデコレーターをlit/decorators.jsから読み込めます。Litは旧来の実験的デコレーターとECMAScript標準のデコレーターの両方に対応しており、標準デコレーターではフィールドにaccessorを付けます。
import {LitElement, html} from 'lit';
import {customElement, property} from 'lit/decorators.js';
@customElement('user-card')
export class UserCard extends LitElement {
@property({type: String}) accessor name = '';
@property({type: Boolean, reflect: true}) accessor active = false;
render() {
return html`<p ?hidden=${!this.active}>${this.name}</p>`;
}
}
この例はexperimentalDecoratorsを指定しない設定(target ES2022)のTypeScript 5.9.3でコンパイルでき、<user-card name="佐藤" active>を描画すると「佐藤」が表示されました。reflect: trueはプロパティの変更を属性へ書き戻す指定で、CSSの属性セレクターでスタイルを切り替えたいときに使います。
公式ドキュメントは、コンパイル結果の効率を理由に、現時点では実験的デコレーターを推奨しています。その場合は"experimentalDecorators": trueと"useDefineForClassFields": falseを組み合わせます。標準デコレーターの型にはTypeScript 5.2以上が必要で、Babelで変換する場合は標準デコレーター(accessor付き)しか使えません。
CDNからビルドなしで読み込む方法
公式は単一ファイルのバンドルも配布しており、npmやビルドツールを使わずに試せます。
<script type="module">
import {LitElement, html} from 'https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.js';
// ここにクラス定義と customElements.define を書く
</script>
npmで入れたlitをビルドせずにブラウザへ渡すと、import {html} from 'lit-html'のような「ベアモジュール指定子」を解決できずに止まります。Vite・webpack・Rollup(@rollup/plugin-node-resolveが必要)でバンドルするか、import mapsで対応表を与えるかのどちらかが必要で、CDN版はこの手間を省いた形です。
Shadow DOMでのスタイルのスコープと外から効かせる方法
LitElementは既定でopenなShadow Rootを作り、その中へ描画します。static stylesに書いたCSSは原則としてShadow DOM内にスコープされます。ただし、外側で指定したcolorやfont-familyなどの継承プロパティは内部にも継承されます。部品同士のクラス名の衝突を防げる反面、ページ側のセレクターでShadow DOM内の要素を直接指定しようとしても適用されません。仕組みそのものはShadow DOMとは?カプセル化の仕組みとopen/closedの違いを実装例で解説で扱っています。
外部からの調整にはCSSカスタムプロパティや、part属性で公開した内部要素を指定する::part()があります。以下では、コンポーネント側のスタイル指定とLight DOMへの切り替えも含めて整理します。
- CSSカスタムプロパティ:
--brand-colorのような変数は継承されてShadow DOMの内側まで届く。テーマの色や余白を外から差し替える正攻法。 - ::slotted()と:host(コンポーネント側):
static stylesの中から、<slot>に差し込まれた子要素やホスト要素自体を指定する。外側のスタイルで上書きできるので、既定値として書く。 - Light DOMへの描画:
createRenderRoot()でthisを返すとShadow DOMを使わずに描画する。ページのCSSがそのまま効くが、スタイルのスコープと<slot>は使えなくなる。
Tailwind CSSのようにページ全体へ出力するユーティリティCSSは、そのままではShadow DOM内に効きません。Litと組み合わせるなら、生成したCSSをunsafeCSSでstatic stylesへ取り込むか、上のLight DOM描画に切り替えるかを決めてから設計します。Tailwind側の版ごとの変更はTailwind CSS v3とv4の違い|破壊的変更と移行手順をv4.3.3の実ビルドで検証を参照してください。
LitのSSRとハイドレーションの現状
サーバー側でLitコンポーネントをHTMLにするには@lit-labs/ssr(4.1.0)を使います。パッケージ名のとおり実験段階のLabs扱いで、ブラウザのDOMを丸ごと再現せず、テンプレートの宣言的な構造を使って文字列を組み立てる仕組みです。先ほどのcounter.jsと同じディレクトリでnpm i @lit-labs/ssrを実行し、package.jsonに"type": "module"を設定します。次のコードをssr.jsとして保存し、node ssr.jsで実行すると、出力例のHTMLが得られます。
import {render} from '@lit-labs/ssr';
import {collectResultSync} from '@lit-labs/ssr/lib/render-result.js';
import {html} from 'lit';
import './counter.js';
console.log(collectResultSync(render(html`<counter-button count="3"></counter-button>`)));
// 出力(抜粋):
// <counter-button count="3"><template shadowroot="open" shadowrootmode="open"><style>...
// ...<button>3回</button>...</template></counter-button>
Shadow DOMは<template shadowrootmode="open">という宣言的Shadow DOMとして書き出され、JavaScriptの読み込み前でも「3回」が表示されます。ブラウザ側で動くようにするハイドレーションの処理は、Lit 3で@lit-labs/ssr-clientへ移りました。
公式ドキュメントが挙げている未対応事項は、非同期処理を伴うコンポーネントと、Shadow DOMを使わないコンポーネントの2つです。前の章のLight DOM描画を選ぶとSSRできなくなるため、両方を使いたい場合は設計の初期に決めておく必要があります。自前のNodeサーバーに組み込むほか、Eleventy・Astro・Next.jsのPages Router(@lit-labs/nextjs)・Nuxt 3向けの連携が公開されています。レンダリング方式の選び方はCSRとSSRの違いと使い分け|レンダリング方式を比較して実装で選ぶ、Astroでの使い方はAstroフレームワークとは?特徴と使い方・Next.jsとの使い分けを解説【2026年版】で扱っています。
React・Vue・Angularとの組み合わせと使い分け
React 19のカスタム要素対応と@lit/reactの要否
React 18までは、JSXに書いた値をすべて属性として渡すため、配列やオブジェクトをLitのプロパティへ渡せず、独自イベントもaddEventListenerで手書きする必要がありました。この穴を埋めるのが@lit/react(1.0.8)のcreateComponent()で、Litの要素をReactコンポーネントで包み、propsをプロパティに、イベントをコールバックに対応付けます。
2024年12月5日に正式版が出たReact 19は、カスタム要素に完全対応したと公式ブログで発表しました。クライアント側では、要素のインスタンスに同名のプロパティがあればプロパティとして、無ければ属性として渡します。サーバー描画では文字列・数値とtrueだけが属性として出力され、オブジェクトや関数は省かれます。React 19以降なら、単純な組み込みにラッパーは不要です。ReactのTypeScriptで要素の型を効かせたい場合や、React 18以前のコードベースを抱える場合は@lit/reactが引き続き役に立ちます。React 19の変更点はReact 19とは?18からの変更点・新機能とReact Compiler・最新バージョンを解説にまとめています。
VueとAngularから使うときの設定
Vueはハイフンを含むタグを自分のコンポーネントとして解決しようとするため、compilerOptions.isCustomElementでLit製の要素を除外します。Angularでは、モジュールまたはスタンドアロンコンポーネントのschemasにCUSTOM_ELEMENTS_SCHEMAを加えると、未知のタグとしてエラーにならなくなります。Viteなどのビルド環境では、この設定をapp.configではなくテンプレートを処理するプラグイン(@vitejs/plugin-vue)のcompilerOptionsに書きます。配列やオブジェクトはプロパティとして渡すのが基本で、Vue 3は要素に同名のプロパティがあれば自動でプロパティとして設定し、判定できない場合だけ.prop修飾子で強制します。Angularは[listItems]="items"の形で書けばプロパティに入ります。同じLit製のUI部品を、この程度の設定でVueとAngularの両方から使い回せます。
Litを選ぶべき場面と避けるべき場面
Litはルーター・状態管理・データ取得を持たない「部品を作るライブラリ」です。Litが向くのは、複数のフレームワークにまたがって同じボタンやフォーム部品を配るデザインシステムや、CMSや既存のサーバー描画ページに部品を後から差し込む場面です。部品のカタログ化にはStorybookとは?使い方・Next.js連携・テスト・費用まで実装手順で解説がWeb Componentsにも対応しています。
逆に、画面遷移や状態管理まで含めたSPAを1つのチームで作るなら、Litで基盤を組み上げる理由は薄いです。チームにReact・Vueの経験があり、既存のルーターや状態管理ライブラリを採用するなら、その構成を使う方が独自に基盤を組み合わせる作業を減らせます(両者の比較はReactとVue3の違いを比較|どっちを選ぶべきか初心者向けに解説)。SEOのためにSSRが前提で、かつ非同期データを描画の中で待つ構成も、前述のLabsの制限にかかるため避けた方が安全です。
Lit 2からLit 3へ移行するときの確認点
公式の移行ガイドは「大半の利用者はコード変更なしで上げられる」としていますが、ビルド環境によっては止まります。確認すべき変更は次のとおりです。
- IE11のサポート終了:旧Edge(Classic Edge)も対象外。どうしても必要ならLit 2に留まる。
- ES2021での公開:webpack 4は
??・??=・?.を解析できず「Module parse failed: Unexpected token」で止まる。webpack 5へ上げるか、babel-loaderでLitのコードだけ変換する。 - デコレーターの統一:TypeScriptは5.2以上が必要。Babelの「2018-09」版デコレーターは使えなくなった。
- SSRのハイドレーション:関連モジュールが
@lit-labs/ssr-clientへ移動した。
Lit 2とLit 3は相互運用でき、テンプレート・基底クラス・ディレクティブを混在させても動きます。部品ライブラリを配布する側は、依存の範囲を"^2.7.0 || ^3.0.0"のように両方へ広げれば、利用者側の移行を待たずに済みます。
Lit実装でつまずきやすい箇所と回避策
JavaScriptでは、リアクティブなプロパティをクラスフィールドで初期化すると更新が動かなくなります。static properties = {foo: {type: String}}と宣言したうえでfoo = 'Default';と書くと、インスタンスに作られたフィールドがプロトタイプ側のアクセサーを覆い隠し、値を変えても再描画されません。初期値は前掲の例のようにconstructorの中で代入します。TypeScriptで実験的デコレーターを使う場合も、useDefineForClassFieldsをfalseにしないと同じ理由で更新が止まります。
CSSの適用範囲とSSRの対応範囲は、描画方式を選ぶ際にまとめて確認する必要があります。ページのCSSを効かせたくてLight DOM描画へ切り替えると、SSRの対象外になり、<slot>も使えません。色や余白をCSSカスタムプロパティで外から渡す設計にしておけば、Shadow DOMを保ったままスタイルの差し替え・SSR・slotを両立できます。
最後に対応ブラウザです。Lit 3は公式の要件によると、Chromium・Safariの最新版と2つ前までのメジャー版、Firefoxの最新版とESRで検証されています。これより古い環境は動く可能性はあっても保証外なので、社内の古い端末を相手にする業務システムでは事前に動作を確認してください。
Litに関するよくある質問
lit-htmlとLitElement(Lit)は何が違いますか?
lit-htmlはテンプレートを描画するだけのライブラリで、LitElementはその描画機能にリアクティブなプロパティとShadow DOMのスタイルを加えた、カスタム要素の基底クラスです。現在は両方がlitパッケージに含まれており、コンポーネントを作るならlitを、テンプレートだけを使うならlit-html単体を入れます。
LitはGoogleのフレームワークですか?
Googleで開発が始まり長く主導してきましたが、2025年10月14日にOpenJS FoundationのImpact Projectへ移り、資産とブランドは財団に移管されました。運営はGoogle・Adobe・Redditの所属者と独立の開発者によるTSCが担います。なお公式はLitをフレームワークでなく「ライブラリ」と呼んでいます。
Litは無料で商用利用できますか?
できます。lit・lit-html・lit-elementなどのパッケージはいずれもBSD-3-Clauseライセンスで、再配布するときは著作権表示とライセンス文を残します。
LitでTailwind CSSは使えますか?
使えますが、ページに読み込んだTailwindのCSSはShadow DOMの中へ届きません。生成したCSSをunsafeCSSで各コンポーネントのstatic stylesへ取り込むか、createRenderRoot()でthisを返してLight DOMに描画するかを選びます。後者はSSRと<slot>が使えなくなる点に注意してください。
LitでSignalsは使えますか?
TC39のSignals提案のポリフィルとLitをつなぐ@lit-labs/signalsが2024年10月に公開されており、最新は0.3.0です。提案自体が初期段階で、パッケージもLabs扱いのため、本番の状態管理に使う前に仕様変更の可能性を織り込んでください。