Color mode

テーマ作成の詳細

このページは、Riebeckite Theme を実際に設計・実装するときの詳細ガイドです。

初めて Theme を作る場合は、先に はじめてのテーマ作成 を読んでください。

このページでは、その先に必要になる、

  • Theme の責務
  • defineTheme
  • Color Mode
  • Typography
  • Layout
  • Design Token
  • Stable CSS Hook
  • Theme 固有 Option
  • CSS Cascade
  • Package としての配布

までをまとめて扱います。

各型やフィールドの完全な定義を確認したい場合は Theme API を参照してください。

このページの構成

1. Theme にするべき変更

Theme は Presentation Layer です。

Site の機能や Content の意味は変更せず、見た目だけを変更します。

Theme でできる Theme ではしない
Token の上書き・追加 Component Replacement
CSS Rule の定義 JSX の注入
Theme 固有 data-* Attribute Route の追加
Color Mode の宣言 Plugin の追加・削除
Typography の宣言 Client Script の実行
Layout Preset の宣言 DOM Transformation
Stable Hook の Styling Island の登録
userCss による最終上書き Filesystem / ContentManager へのアクセス

迷った場合は、次のように判断します。

Diagram source
text
flowchart TD
    Q{"何を変更したい?"}
 
    Q -->|"見た目"| Theme["Theme"]
    Q -->|"再利用可能な機能"| Plugin["Plugin"]
    Q -->|"Site固有Route / Layout構造"| App["Application"]
    Q -->|"Framework共通Model"| Core["Core"]

基本的には、

text
機能
  → Plugin
 
見た目
  → Theme
 
Site 固有 Route
  → Application

です。

見た目を変えるためだけに Plugin を作ったり、機能を追加するために Theme を拡張したりしないでください。

2. 最小の Theme

Theme は defineTheme() で定義します。

ts
import { defineTheme } from "@riebeckite/core";
 
export function exampleTheme() {
  return defineTheme({
    name: "example",
 
    styles: [
      {
        moduleSpecifier:
          "@riebeckite/theme-example/style.css",
      },
    ],
  });
}

Site では theme に指定します。

ts
export default defineConfig({
  theme: exampleTheme(),
});

これが最小構成です。

3. defineTheme の Contract

Theme が扱う主な Contract は次のとおりです。

領域 内容
Identity name
Theme 固有設定 options
CSS styles[].moduleSpecifier
共通設定 colorMode, typography, articleLayout, tokens, userCss
Attributes 安全な data-* Attribute

たとえば、

ts
import { defineTheme } from "@riebeckite/core";
 
export function exampleTheme() {
  return defineTheme({
    name: "example",
 
    options: {
      // Theme 固有 Option
    },
 
    styles: [
      {
        moduleSpecifier:
          "@riebeckite/theme-example/style.css",
      },
    ],
 
    attributes: {
      "data-example-flag": "on",
    },
  });
}

のように定義できます。

styles[].moduleSpecifier は Host Bundler が解決する Module Specifier です。

CSS File を Application Directory へコピーするための Path ではありません。

4. Site 内だけで使う Theme

Theme は npm Package として公開しなくても利用できます。

たとえば、

text
site/
└─ extensions/
   ├─ local-theme.ts
   └─ theme.css

のように Site 内へ置けます。

ts
// site/extensions/local-theme.ts
 
import { defineTheme } from "@riebeckite/core";
 
export function localTheme() {
  return defineTheme({
    name: "site-local",
 
    styles: [
      {
        moduleSpecifier:
          "/extensions/theme.css",
      },
    ],
 
    attributes: {
      "data-site-local": "on",
    },
  });
}

Site-local Theme の、

  • name
  • styles
  • attributes
  • tokens

も Published Theme と同じ resolveThemeConfig の経路で解決・sanitize・適用されます。

19. Theme Factory Options

Theme 固有の機能は Factory Option として定義します。

ts
type NewspaperOptions = {
  density?:
    | "compact"
    | "comfortable";
};
 
export function newspaperTheme(
  options: NewspaperOptions = {},
) {
  return defineTheme({
    name: "newspaper",
 
    options,
 
    attributes: {
      "data-newspaper-density":
        options.density
        ?? "comfortable",
    },
 
    styles: [
      {
        moduleSpecifier:
          "@riebeckite/theme-newspaper/style.css",
      },
    ],
  });
}

この Option は Core の ThemeConfig に追加しません。

text
newspaper の density
  → newspaperTheme が所有
 
tokyonight の neon
  → tokyonightTheme が所有

Theme 固有の概念は、その Theme Package 内で完結させます。

31. Theme を作るときの基本方針

Theme の実装では、最終的に次の境界を維持することが重要です。

Diagram source
text
flowchart LR
    App["Application"]
    Plugin["Plugin"]
 
    App --> Hooks["Stable Hooks"]
    Plugin --> Hooks
 
    Core["Core"] --> Tokens["Semantic Tokens"]
 
    Hooks --> Contract["Presentation Contract"]
    Tokens --> Contract
 
    Theme["Theme"] --> Contract
 
    Contract --> Site["Final Site"]

Theme は Application や Plugin の内部構造を所有しません。

Framework と Plugin が公開した、

text
Stable CSS Hooks
Semantic Design Tokens
Theme Attributes
CSS Cascade

という Presentation Contract を利用します。

Theme 固有の設定は Theme Package 内に閉じ込め、Core へ漏らしません。

そして、Theme の変更によって、

text
Content
Route
Manifest
Content Graph
Plugin Behavior
Client Behavior

が変化しない状態を維持してください。

機能は Plugin、構造は Framework / Application、見た目は Theme

という境界を守ることで、Theme を交換しても同じ Site と Plugin をそのまま利用できます。

関連資料

History

1 changesCollapseExpand
1 + ---
2 + title: テーマ作成の詳細
3 + sidebar:
4 + label: テーマ作成の詳細
5 + ---
6 + # テーマ作成の詳細
7 +
8 + このページは、Riebeckite Theme を実際に設計・実装するときの詳細ガイドです。
9 +
10 + 初めて Theme を作る場合は、先に [はじめてのテーマ作成](../themes/writing-a-theme.ja.md) を読んでください。
11 +
12 + このページでは、その先に必要になる、
13 +
14 + - Theme の責務
15 + - `defineTheme`
16 + - Color Mode
17 + - Typography
18 + - Layout
19 + - Design Token
20 + - Stable CSS Hook
21 + - Theme 固有 Option
22 + - CSS Cascade
23 + - Package としての配布
24 +
25 + までをまとめて扱います。
26 +
27 + 各型やフィールドの完全な定義を確認したい場合は [Theme API](../reference/theme-api.ja.md) を参照してください。
28 +
29 +
30 + ## このページの構成
31 +
32 + - [Color Mode](./theme-system/color-mode.ja.md)
33 + - [Typography と Article Layout](./theme-system/typography.ja.md)
34 + - [Design Tokens](./theme-system/tokens.ja.md)
35 + - [Theme Root と Attributes](./theme-system/theme-root.ja.md)
36 + - [Stable CSS Hooks と Cascade](./theme-system/css.ja.md)
37 + - [テーマの配布と検証](./theme-system/distribution.ja.md)
38 +
39 + ## 1. Theme にするべき変更
40 +
41 + Theme は **Presentation Layer** です。
42 +
43 + Site の機能や Content の意味は変更せず、見た目だけを変更します。
44 +
45 + | Theme でできる | Theme ではしない |
46 + | --- | --- |
47 + | Token の上書き・追加 | Component Replacement |
48 + | CSS Rule の定義 | JSX の注入 |
49 + | Theme 固有 `data-*` Attribute | Route の追加 |
50 + | Color Mode の宣言 | Plugin の追加・削除 |
51 + | Typography の宣言 | Client Script の実行 |
52 + | Layout Preset の宣言 | DOM Transformation |
53 + | Stable Hook の Styling | Island の登録 |
54 + | `userCss` による最終上書き | Filesystem / ContentManager へのアクセス |
55 +
56 + 迷った場合は、次のように判断します。
57 +
58 + ```mermaid id="5c6pd8"
59 + flowchart TD
60 + Q{"何を変更したい?"}
61 +
62 + Q -->|"見た目"| Theme["Theme"]
63 + Q -->|"再利用可能な機能"| Plugin["Plugin"]
64 + Q -->|"Site固有Route / Layout構造"| App["Application"]
65 + Q -->|"Framework共通Model"| Core["Core"]
66 + ```
67 +
68 + 基本的には、
69 +
70 + ```text id="kl9kkm"
71 + 機能
72 + → Plugin
73 +
74 + 見た目
75 + → Theme
76 +
77 + Site 固有 Route
78 + → Application
79 + ```
80 +
81 + です。
82 +
83 + 見た目を変えるためだけに Plugin を作ったり、機能を追加するために Theme を拡張したりしないでください。
84 +
85 +
86 + ## 2. 最小の Theme
87 +
88 + Theme は `defineTheme()` で定義します。
89 +
90 + ```ts id="yplspq"
91 + import { defineTheme } from "@riebeckite/core";
92 +
93 + export function exampleTheme() {
94 + return defineTheme({
95 + name: "example",
96 +
97 + styles: [
98 + {
99 + moduleSpecifier:
100 + "@riebeckite/theme-example/style.css",
101 + },
102 + ],
103 + });
104 + }
105 + ```
106 +
107 + Site では `theme` に指定します。
108 +
109 + ```ts id="ij1yvm"
110 + export default defineConfig({
111 + theme: exampleTheme(),
112 + });
113 + ```
114 +
115 + これが最小構成です。
116 +
117 +
118 + ## 3. `defineTheme` の Contract
119 +
120 + Theme が扱う主な Contract は次のとおりです。
121 +
122 + | 領域 | 内容 |
123 + | --- | --- |
124 + | Identity | `name` |
125 + | Theme 固有設定 | `options` |
126 + | CSS | `styles[].moduleSpecifier` |
127 + | 共通設定 | `colorMode`, `typography`, `articleLayout`, `tokens`, `userCss` |
128 + | Attributes | 安全な `data-*` Attribute |
129 +
130 + たとえば、
131 +
132 + ```ts id="k0nh8m"
133 + import { defineTheme } from "@riebeckite/core";
134 +
135 + export function exampleTheme() {
136 + return defineTheme({
137 + name: "example",
138 +
139 + options: {
140 + // Theme 固有 Option
141 + },
142 +
143 + styles: [
144 + {
145 + moduleSpecifier:
146 + "@riebeckite/theme-example/style.css",
147 + },
148 + ],
149 +
150 + attributes: {
151 + "data-example-flag": "on",
152 + },
153 + });
154 + }
155 + ```
156 +
157 + のように定義できます。
158 +
159 + `styles[].moduleSpecifier` は Host Bundler が解決する Module Specifier です。
160 +
161 + CSS File を Application Directory へコピーするための Path ではありません。
162 +
163 +
164 + ## 4. Site 内だけで使う Theme
165 +
166 + Theme は npm Package として公開しなくても利用できます。
167 +
168 + たとえば、
169 +
170 + ```text id="10b70z"
171 + site/
172 + └─ extensions/
173 + ├─ local-theme.ts
174 + └─ theme.css
175 + ```
176 +
177 + のように Site 内へ置けます。
178 +
179 + ```ts id="31fzcq"
180 + // site/extensions/local-theme.ts
181 +
182 + import { defineTheme } from "@riebeckite/core";
183 +
184 + export function localTheme() {
185 + return defineTheme({
186 + name: "site-local",
187 +
188 + styles: [
189 + {
190 + moduleSpecifier:
191 + "/extensions/theme.css",
192 + },
193 + ],
194 +
195 + attributes: {
196 + "data-site-local": "on",
197 + },
198 + });
199 + }
200 + ```
201 +
202 + Site-local Theme の、
203 +
204 + - `name`
205 + - `styles`
206 + - `attributes`
207 + - `tokens`
208 +
209 + も Published Theme と同じ `resolveThemeConfig` の経路で解決・sanitize・適用されます。
210 +
211 +
212 + ## 19. Theme Factory Options
213 +
214 + Theme 固有の機能は Factory Option として定義します。
215 +
216 + ```ts id="94pd1f"
217 + type NewspaperOptions = {
218 + density?:
219 + | "compact"
220 + | "comfortable";
221 + };
222 +
223 + export function newspaperTheme(
224 + options: NewspaperOptions = {},
225 + ) {
226 + return defineTheme({
227 + name: "newspaper",
228 +
229 + options,
230 +
231 + attributes: {
232 + "data-newspaper-density":
233 + options.density
234 + ?? "comfortable",
235 + },
236 +
237 + styles: [
238 + {
239 + moduleSpecifier:
240 + "@riebeckite/theme-newspaper/style.css",
241 + },
242 + ],
243 + });
244 + }
245 + ```
246 +
247 + この Option は Core の `ThemeConfig` に追加しません。
248 +
249 + ```text id="2cqt02"
250 + newspaper の density
251 + → newspaperTheme が所有
252 +
253 + tokyonight の neon
254 + → tokyonightTheme が所有
255 + ```
256 +
257 + Theme 固有の概念は、その Theme Package 内で完結させます。
258 +
259 +
260 + ## 31. Theme を作るときの基本方針
261 +
262 + Theme の実装では、最終的に次の境界を維持することが重要です。
263 +
264 + ```mermaid id="sdf2dm"
265 + flowchart LR
266 + App["Application"]
267 + Plugin["Plugin"]
268 +
269 + App --> Hooks["Stable Hooks"]
270 + Plugin --> Hooks
271 +
272 + Core["Core"] --> Tokens["Semantic Tokens"]
273 +
274 + Hooks --> Contract["Presentation Contract"]
275 + Tokens --> Contract
276 +
277 + Theme["Theme"] --> Contract
278 +
279 + Contract --> Site["Final Site"]
280 + ```
281 +
282 + Theme は Application や Plugin の内部構造を所有しません。
283 +
284 + Framework と Plugin が公開した、
285 +
286 + ```text id="ysb8qa"
287 + Stable CSS Hooks
288 + Semantic Design Tokens
289 + Theme Attributes
290 + CSS Cascade
291 + ```
292 +
293 + という Presentation Contract を利用します。
294 +
295 + Theme 固有の設定は Theme Package 内に閉じ込め、Core へ漏らしません。
296 +
297 + そして、Theme の変更によって、
298 +
299 + ```text id="e6c5sk"
300 + Content
301 + Route
302 + Manifest
303 + Content Graph
304 + Plugin Behavior
305 + Client Behavior
306 + ```
307 +
308 + が変化しない状態を維持してください。
309 +
310 + **機能は Plugin、構造は Framework / Application、見た目は Theme**
311 +
312 + という境界を守ることで、Theme を交換しても同じ Site と Plugin をそのまま利用できます。
313 +
314 +
315 + ## 関連資料
316 +
317 + - [はじめてのテーマ作成](../themes/writing-a-theme.ja.md) — 最初の Theme を作る
318 + - [Theme System](./theme-system.ja.md) — Theme System 全体の考え方
319 + - [Plugin System](./plugin-system.ja.md) — Plugin との責務の違い
320 + - [Framework Reference](../reference/README.ja.md) — `defineTheme` などの Public API
321 +