**テーマパック**は、スタイルシート・メタデータファイル・オプションのセルフホストフォントをひとまとめにした、インストール可能なデザインバンドルです。デザイントークン、フォントスタック、コンポーネントのディテールまで、サイト全体の見た目を一手で切り替えます。パックは `@takazudo/zudo-doc` に同梱されており、設定フィールド 1 つで選択でき、オプションのページ内スイッチャーからライブで切り替えることもできます。

## テーマパックとカラースキームの違い

zudo-doc のテーマ機構は 2 つのレイヤーからなり、両者は契約上独立しています:

- **カラースキーム**はランプネイティブな**ライト/ダークのパレット**です — `colorScheme`/`colorMode` 設定フィールド、ヘッダーの外観メニュー（Light / Dark / System）、`light-dark()` による解決がこの層に属します。サイトにとっての「ライト」「ダーク」が何色なのかを決めるのはこの層です。[Color](https://zudo-doc.takazudomodular.com/ja/docs/reference/color) を参照してください。
- **テーマパック**はその上に重なる**インストール可能なデザインバンドル**です。パックはデザイントークンを上書きし、フォントスタックを差し替え、コンポーネントを再スタイルできます — そしてすべての色を `light-dark()` でライト/ダーク**両方**定義するため、外観メニューのすべての選択がどのパックの上でもそのまま機能します。

パックを切り替えても選択中の外観設定には触れませんし、外観を選んでもアクティブなパックには触れません。System は選択中のパックに関係なくデバイス設定に従います。

|                | カラースキーム（モードシステム）  | テーマパック                                   |
| -------------- | -------------------------------- | ---------------------------------------------- |
| 設定フィールド | `colorScheme`、`colorMode`       | `themePack`、`themePackSwitcher`、`themePacks` |
| DOM 属性       | `<html data-theme>`              | `<html data-theme-pack>`                       |
| 永続化キー     | `localStorage["zudo-doc-theme"]` | `localStorage["zudo-doc-theme-pack"]`          |
| 変更イベント   | `color-scheme-changed`           | `theme-pack-changed`                           |

[Dynamic content unavailable in text export]

単独の「テーマ」という語は、常にライト/ダークのモードシステムを指します。パック層の語彙はすべて**テーマパック**です — `data-theme` や `color-scheme-changed` はテーマパック API の一部ではなく、パックがこれらを読み書きすることもありません。



## 同梱パック

`@takazudo/zudo-doc` には 31 のパックが同梱されています — 各パックのバッジ・フォント・適用スニペットを載せた全カタログは[テーマギャラリー](https://zudo-doc.takazudomodular.com/ja/docs/reference/theme-gallery)にあります。コレクションの中で特別な役割を持つのは次の 2 つです:

| スラッグ  | 説明                                                                                                                                                                                      |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `default` | zudo-doc 標準の見た目。予約されたパックで、メタデータのみを同梱します — このパックの**スタイルシートが読み込まれることは決してない**ため、標準の見た目に追加リクエストは一切発生しません。 |
| `foundry` | リファレンスパック。GitHub ニュートラルなベースライン — 白い紙面、ほぼ黒のインク、Primer ブルーのリンク — で、9 つの `--zd-syntax-*` コードハイライトロールを含むすべてのトークン面を、ライト/ダーク両モードで再スタイルします。 |

## パックの選択

この機能は 3 つの設定フィールドで制御します:

| フィールド          | デフォルト  | 役割                                                                                                                             |
| ------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `themePack`         | `"default"` | アクティブなパックのスラッグ。                                                                                                     |
| `themePackSwitcher` | `false`     | 右下のスイッチャーフライアウト（と一覧ダイアログ）を全ページにマウントする。                                                       |
| `themePacks`        | `undefined` | 有効化するパックのスラッグをスイッチャー順で並べたリスト。`undefined` は同梱パックすべて — `"default"` が先頭、残りはアルファベット順。 |

```ts title="zfb.config.ts"
export default defineConfig(
  zudoDoc({
    siteName: "My Docs",
    themePack: "foundry",
    themePackSwitcher: true,
    themePacks: ["default", "foundry"],
  }),
);
```

- 明示的な `themePacks` リストが正となります。`"default"` を省いても構いませんし、その並び順が**そのまま**スイッチャーの Prev/Next の巡回順と一覧ダイアログのグリッド順になります。
- `themePack` や `themePacks` に未知のスラッグや重複したスラッグがあると、プラグインのセットアップ時点で**ビルドが大きな声で失敗**し、問題のスラッグと利用可能なスラッグの一覧が表示されます。ビルド時のサイレントフォールバックはありません。
- `themePackSwitcher` を `false` のままにして `themePack` に default 以外のパックを指定することもできます。切り替え UI を持たない、ビルドで固定された見た目です。

## スイッチャー UI

`themePackSwitcher: true` にすると、ビューポート右下に小さな丸いランチャーボタンが固定表示されます。クリックするとフライアウトカードが開き、アクティブなパックの名前、**Light/Dark バッジ**（そのパックが主に想定するモード）、説明文に加えて、次の操作が並びます:

- **Prev / Next** — 有効なパックを `themePacks` の順で巡回します（末尾で折り返し）。
- **一覧表示**（グリッドアイコン）— 後述のグリッドダイアログを開きます。
- **✕ / Esc** — カードを閉じます。

カードの表示は切り替えが実際にコミットされたときだけ進みます — パックのスタイルシートの読み込みに失敗した場合、現在のパックが適用されたままで UI も動きません。

### 一覧ダイアログ

グリッドダイアログには有効なパックごとに 1 枚のカードが並びます — ミニプレビュー（見出しサンプル、本文の 1 行、そのパックのシンタックスカラーで彩色されたコード行）、パック名、Light/Dark バッジ、説明文です。2 つの挙動は意図的なものです:

- **一覧表示のためのダウンロードはゼロ。** カードは各パックのメタデータに含まれる解決済みプレビュースウォッチから、現在のライト/ダークモードに合わせて描画されます — カタログ全体を眺めても、パックのスタイルシートも Web フォントも一切取得されません。
- **適用してもダイアログは開いたまま。** カードをクリックするとそのパックが即座にページへ適用されますが、ダイアログは開いたままなので、見た目を並べて比較できます。閉じるのは Esc、✕ ボタン、または背景クリックです。アクティブなカードには選択リングが付き、どの UI から切り替えても追従します。

### 永続化とランタイム挙動

- 読者の選択は `localStorage["zudo-doc-theme-pack"]` に保持され、描画前のブートストラップが再適用します — ハードロードでも最初のフレームから正しいパックで描画され（デフォルトの見た目が一瞬見えるフラッシュはありません）、SPA ナビゲーションもまたぎます。JavaScript 無効の訪問者には設定された `themePack` が表示されます。
- 保存済みスラッグが有効なパックでなくなった場合（パックの削除、タイプミス、改竄）は、設定された `themePack` へ静かにフォールバックします。
- ダウンロードされるのは常に**アクティブな**パックの CSS とフォントだけです — 切り替え時は次のパックのスタイルシートを取得し、読み込み完了を待ってからアトミックに切り替わります。`default` パックは何も読み込みません。
- テストや連携向けの契約: アクティブなスラッグは常に `<html data-theme-pack="…">` から読み取れ、コミットされた切り替えのたびに `window` 上で `theme-pack-changed` イベント（`detail: { pack, previous }`）が発火します。

## theme CLI

`@takazudo/zudo-doc` の `zudo-doc` bin には `theme` サブコマンドがあります。**完全オフライン**で動作します — カタログはインストール済みパッケージから、アクティブなパックはプロジェクトの `zfb.config.ts` から読み取り、ネットワークには何も取りに行きません。

```sh
# list the installed packs and which one is active
pnpm exec zudo-doc theme list

# switch the project to the foundry pack
pnpm exec zudo-doc theme apply foundry
```

`theme list` は、インストール済み各パックのスラッグ・名前・モード・バージョン・説明を表示し、プロジェクトが現在設定しているパックに印を付けます。

`theme apply <slug>` の挙動:

- まずスラッグをインストール済みカタログに対して検証します — 未知のスラッグは、利用可能なスラッグの一覧とともに拒否されます。
- `zfb.config.ts` の `zudoDoc({ … })` 呼び出し内の `themePack` フィールドを書き換えます。フィールドがなければ追加します。
- 設定が正規のスキャフォールド形でない場合（たとえば設定がスプレッドの向こうに隠れている場合）は**きっぱり拒否**します — 理由を説明し、手作業で加えるべき 1 行を提示し、中途半端な編集は決して書き込みません。
- 冪等です — すでに設定済みのスラッグを再適用してもファイルは変わりません。
- プロベナンス（スラッグ、パックのバージョン、タイムスタンプ）を `.zudo-doc.json` に記録します。`zudo-doc eject` が使うのと同じ、遅延生成されるファイルです。

## パック指定でのスキャフォールド

`create-zudo-doc` は、同梱パックのどれを起点にしたプロジェクトでも生成できます — `--theme-pack <slug>` フラグ、または対話式の「Theme pack:」プロンプト（カラースキームの質問と機能のマルチセレクトの間に表示されます）で指定します。スイッチャー UI は独立した機能トグルです。[create-zudo-doc CLI リファレンス](https://zudo-doc.takazudomodular.com/ja/docs/reference/create-zudo-doc#%E3%83%86%E3%83%BC%E3%83%9E%E3%83%91%E3%83%83%E3%82%AF)を参照してください。

```sh
pnpm create zudo-doc my-docs --theme-pack foundry --theme-pack-switcher
```

## ライト/ダークと Design Token Panel

- パックの **Light/Dark バッジ**は、そのパックがどちらのモードを主として設計されたかのヒントであって、対応可否のフラグではありません。すべてのパックが両モードを定義しており、ヘッダーの外観メニューはどのパックでも同じように機能します。
- [Design Token Panel](https://zudo-doc.takazudomodular.com/ja/docs/reference/design-token-panel) の調整は**パックごと**に保存されます。パック A がアクティブなときに保存した上書きはパック A に紐づき、別のパックの間は隠れ、A に戻すとそのまま復元されます。`default` パックではパネルは従来のストレージを使い続けるため、テーマパック導入以前に保存した調整はアップグレード後もそのまま生き残ります。

## パックのスタイルシートを自分で出力する

ほとんどのプロジェクトにこの節は不要です。関係するのは、**ホスト（埋め込み側）がパックのスタイルシート `<link>` を自分で出力する**ケースだけで、典型的には zfb のヘッド差し替えを越えてそのリンクを生き残らせたい場合がこれにあたります。このとき描画前のブートストラップは、自前のリンクを挿入せずホスト側のリンクをそのまま使いますが、それが成立するには順序についての厳密な契約があります。

- **zudo-doc のヘッドブートストラップより前に出力し、`data-zd-theme-pack-css` を付けること。** ブートストラップは、この属性を持ち `href` が解決済みパックと一致する `<link>` をヘッドから探します。ホスト側のリンクが見つかれば何も挿入しません — 要素 1 つ、リクエスト 1 回です。
- **href はキャッシュバスティングのクエリまで含めて完全に一致させること。** 形式は `{base}theme-packs/<slug>/pack.css?v=<パックのバージョン>` で、`{base}` は末尾のスラッシュを含むサイトのベースパス、バージョンはパックの `meta.json` に記載されたものです。判定は文字列の完全一致なので、`?v=` を欠いたリンクは別のスタイルシートとみなされ、結果として 2 つとも残ります。
- **`data-zfb-transition-persist` だけでは足りません。** 判定のキーは `data-zd-theme-pack-css` であり、zfb のトランジション永続化のためだけにタグ付けされたリンクはこの判定から見えません。
- **ブートストラップより後に出力すると、コールドブートでは 1 つの href に対して `<link>` 要素が 2 つ発生します。** 先に走るブートストラップからは、パーサーがまだ到達していないリンクが見えないためです。しかも最初の描画までに挽回することもできません — パース後のハンドラが 2 つの要素をまとめられる時点では、パーサーはすでに両方を確定させているからです。2 回目のリクエストが実際に飛ぶかどうかは配信方法次第です。通常は重複した href がキャッシュヒットになるため、コストは余分な要素だけで済みます。ただし `Cache-Control: no-store` で配信している場合は本当に 2 回取得され、元の報告もその条件下で計測されたものです（同じ URL・同じ転送量が、コールドブートごとに 2 回）。

```html
<!-- must precede the zudo-doc head bootstrap; slug and ?v= are YOUR pack's, read from its meta.json -->
<link rel="stylesheet" data-zd-theme-pack-css href="/theme-packs/my-pack/pack.css?v=1.0.0" />
```

## パックの作り方

パックは `theme-packs/<slug>/` という 1 つのディレクトリで、`pack.css`、`meta.json`、オプションの `fonts/` フォルダを持ちます。`meta.json` はパックのアイデンティティ（スラッグ、表示名、説明、Light/Dark バッジ、バージョン）に加えて、一覧ダイアログがカードを描画するための解決済みプレビュースウォッチを運びます。

```css title="theme-packs/my-pack/pack.css"
html[data-theme-pack="my-pack"] {
  --zd-bg: light-dark(#ffffff, #0d1117);
  --zd-accent: light-dark(#0969da, #4493f8);
  --zd-syntax-keyword: light-dark(#cf222e, #ff7b72);
  --font-sans: "Jost", system-ui, sans-serif;
  --zdc-doc-title-weight: 600;
}
```

オーサリング契約の要点:

- **すべてのルールを** `html[data-theme-pack="<slug>"]` **でスコープする。** これが切り替えをアトミックにし、非アクティブなパックがスタイルを漏らさないことを保証します。
- **すべての色を** `light-dark(lightValue, darkValue)` **で両モード定義する。** パックは `color-scheme:` を宣言せず、`[data-theme]` セレクタも使いません — モードはあくまでトグルの仕事です。
- **上書きするのは `--zd-*` セマンティックロール**（9 つの `--zd-syntax-*` ハイライトロールを含む）**と `--zdc-*` コンポーネントシーム。** `--color-*` のユーティリティエイリアスは決して上書きしません — Design Token Panel のライブ編集ラウンドトリップが壊れます。
- **追加の装飾は安定した DOM フックに対してのみ**（`header[data-header]`、`#desktop-sidebar`、`[data-admonition]`、`pre.hi-root` など）— zudo-wind ユーティリティクラス名に対しては決して書きません。
- **`[data-header-logo]` のアンカーはブロックレベルのボックスのまま保つ**（`display` は `block`／`inline`／`inline-block`／`flow-root` のいずれかとし、`overflow`／`white-space`／`text-overflow` も上書きしない）— ヘッダーコンポーネント自身の `truncate` クラスが、サイト名が長いときの省略記号（…）表示にこれを必要とします。ロゴのマーカー（四角形やドットなど）はアンカー自体を flex や grid コンテナに変えるのではなく、`::before`／`::after` に置いてください。

`--font-sans`の上書きは、`body { font-family: var(--zdc-chrome-font, var(--font-sans)) }`を通じてプロズとクローム全体に届きます。任意の`--zdc-header-font`、`--zdc-sidebar-font`、`--zdc-toc-font`で特定のサーフェスだけ別の書体にできます。コンポーネントのセレクターはデスクトップとモバイルのクロームを対象にします。ビルドしたページと繰り返しのクライアントナビゲーションで computed value を確認してください。実効的な identity と props が変わらないアイランドは保持され、変更時は描画スコープが再作成されます。

- **フォントはセルフホストの OFL ライセンス Google Fonts かシステムスタックのみ。** `font-display: swap` を付け、フォールバックスタックは必ず総称ファミリーで終端します。商用フォントを前提にすることも参照することもありません（`local("Futura")` のような記述は禁止）— Futura 風のパックは OFL の代替である **Jost** を読み込みます。これにより、すべてのパックがオフライン（Tauri リーダー）でも厳格な CSP の下でも同一に描画されます。

ビルド時のバリデーターがこの契約全体 — スコープ、トークン名のマニフェスト、フォントの整合、商用フォントの拒否リスト — を強制するため、壊れたパックはパッケージのビルドを失敗させ、あなたのサイトのビルドを壊すことはありません。アーキテクチャの全容（メタデータスキーマ、切り替えアルゴリズム、バリデータールール）は [theme-packs ADR](https://github.com/zudolab/zudo-doc/blob/main/packages/zudo-doc/docs/adr/theme-packs.md) に確定仕様として記録されています。

## 関連項目

- [テーマギャラリー](https://zudo-doc.takazudomodular.com/ja/docs/reference/theme-gallery) — 同梱パックの全カタログ
- [Color](https://zudo-doc.takazudomodular.com/ja/docs/reference/color) — パックの土台となるカラースキーム層
- [Design Token Panel](https://zudo-doc.takazudomodular.com/ja/docs/reference/design-token-panel) — トークンのライブ調整。調整はパックごとに保存されます
- [zudo-doc のカスタマイズ](https://zudo-doc.takazudomodular.com/ja/docs/reference/customizing) — エスカレーションはしごの中でのテーマパックの位置
- [create-zudo-doc CLI](https://zudo-doc.takazudomodular.com/ja/docs/reference/create-zudo-doc) — `--theme-pack` でのスキャフォールド
- [設定](https://zudo-doc.takazudomodular.com/ja/docs/guides/configuration) — `zudoDoc()` 全フィールドのリファレンス