0
# Theme System
1
2
The theme system provides comprehensive styling and theming capabilities with JupyterLab integration, CSS variable-based themes, and custom theme support.
3
4
## Capabilities
5
6
### EditorThemeRegistry
7
8
Registry for managing editor themes with support for custom themes and JupyterLab integration.
9
10
```typescript { .api }
11
/**
12
* CodeMirror 6 theme registry
13
* Manages themes with JupyterLab integration and CSS variables
14
*/
15
class EditorThemeRegistry implements IEditorThemeRegistry {
16
constructor();
17
18
/**
19
* Get all registered themes
20
*/
21
readonly themes: IEditorTheme[];
22
23
/**
24
* Get the default CodeMirror 6 theme for JupyterLab
25
*/
26
defaultTheme(): Extension;
27
28
/**
29
* Register a new theme
30
*/
31
addTheme(theme: IEditorTheme): void;
32
33
/**
34
* Get a theme by name (falls back to default theme if not found)
35
*/
36
getTheme(name: string): Extension;
37
}
38
39
interface IEditorThemeRegistry {
40
readonly themes: IEditorTheme[];
41
defaultTheme(): Extension;
42
addTheme(theme: IEditorTheme): void;
43
getTheme(name: string): Extension;
44
}
45
```
46
47
**Usage Examples:**
48
49
```typescript
50
import { EditorThemeRegistry } from "@jupyterlab/codemirror";
51
52
// Create theme registry
53
const themes = new EditorThemeRegistry();
54
55
// Get default JupyterLab theme
56
const defaultTheme = themes.defaultTheme();
57
58
// Add custom theme
59
themes.addTheme({
60
name: 'my-dark-theme',
61
displayName: 'My Dark Theme',
62
theme: myDarkThemeExtension
63
});
64
65
// Get theme by name
66
const darkTheme = themes.getTheme('my-dark-theme');
67
const fallbackTheme = themes.getTheme('nonexistent'); // Returns default theme
68
69
// List all themes
70
console.log(themes.themes.map(t => t.name));
71
```
72
73
### Built-in JupyterLab Theme
74
75
Complete JupyterLab-integrated theme with CSS variables and syntax highlighting.
76
77
```typescript { .api }
78
/**
79
* Complete JupyterLab CodeMirror 6 theme
80
* Combines editor theme and syntax highlighting
81
*/
82
const jupyterTheme: Extension;
83
84
/**
85
* JupyterLab editor theme definition with CSS variables
86
*/
87
const jupyterEditorTheme: EditorView.theme;
88
89
/**
90
* Syntax highlighting style for JupyterLab using CSS variables
91
*/
92
const jupyterHighlightStyle: HighlightStyle;
93
```
94
95
**Usage Examples:**
96
97
```typescript
98
import {
99
jupyterTheme,
100
jupyterEditorTheme,
101
jupyterHighlightStyle
102
} from "@jupyterlab/codemirror";
103
import { EditorView } from "@codemirror/view";
104
import { syntaxHighlighting } from "@codemirror/language";
105
106
// Use complete JupyterLab theme
107
const editor = new EditorView({
108
extensions: [
109
jupyterTheme,
110
// ... other extensions
111
],
112
parent: document.body
113
});
114
115
// Use components separately
116
const customTheme = [
117
jupyterEditorTheme,
118
syntaxHighlighting(jupyterHighlightStyle)
119
];
120
121
// Extend JupyterLab theme
122
const extendedTheme = [
123
jupyterTheme,
124
EditorView.theme({
125
// Additional custom styles
126
'.cm-editor': {
127
fontSize: '16px'
128
},
129
'.cm-focused': {
130
outline: '2px solid var(--jp-brand-color1)'
131
}
132
})
133
];
134
```
135
136
### Theme Interface
137
138
Interface for defining custom themes.
139
140
```typescript { .api }
141
/**
142
* Editor theme interface
143
*/
144
interface IEditorTheme {
145
/**
146
* Theme unique identifier
147
*/
148
readonly name: string;
149
150
/**
151
* Theme display name (optional)
152
*/
153
readonly displayName?: string;
154
155
/**
156
* Editor extension for the theme
157
*/
158
readonly theme: Extension;
159
}
160
```
161
162
### Default Themes
163
164
Built-in theme collection with JupyterLab integration.
165
166
```typescript { .api }
167
/**
168
* Get the default editor themes
169
* Returns array including CodeMirror default theme
170
*/
171
static getDefaultThemes(translator?: ITranslator | null): ReadonlyArray<Readonly<IEditorTheme>>;
172
```
173
174
**Usage Examples:**
175
176
```typescript
177
import { EditorThemeRegistry } from "@jupyterlab/codemirror";
178
179
// Get default themes
180
const defaultThemes = EditorThemeRegistry.getDefaultThemes();
181
182
// Create registry with default themes
183
const registry = new EditorThemeRegistry();
184
defaultThemes.forEach(theme => registry.addTheme(theme));
185
186
// Get default themes with translation
187
const translatedThemes = EditorThemeRegistry.getDefaultThemes(translator);
188
```
189
190
### Custom Theme Creation
191
192
Creating custom themes with CSS variables and syntax highlighting.
193
194
```typescript
195
import { EditorView } from "@codemirror/view";
196
import { HighlightStyle, syntaxHighlighting } from "@codemirror/language";
197
import { tags } from "@lezer/highlight";
198
199
// Create custom editor theme
200
const darkEditorTheme = EditorView.theme({
201
'&': {
202
color: '#f8f8f2',
203
backgroundColor: '#272822'
204
},
205
'.cm-content': {
206
padding: '16px',
207
caretColor: '#f92672'
208
},
209
'.cm-focused .cm-cursor': {
210
borderLeftColor: '#f92672'
211
},
212
'.cm-focused .cm-selectionBackground, ::selection': {
213
backgroundColor: '#49483e'
214
},
215
'.cm-panels': {
216
backgroundColor: '#272822',
217
color: '#f8f8f2'
218
},
219
'.cm-searchMatch': {
220
backgroundColor: '#49483e',
221
outline: '1px solid #f92672'
222
},
223
'.cm-activeLine': {
224
backgroundColor: '#49483e22'
225
},
226
'.cm-gutters': {
227
backgroundColor: '#272822',
228
color: '#75715e',
229
border: 'none'
230
},
231
'.cm-activeLineGutter': {
232
backgroundColor: '#49483e22'
233
}
234
}, { dark: true });
235
236
// Create custom syntax highlighting
237
const darkHighlightStyle = HighlightStyle.define([
238
{ tag: tags.keyword, color: '#f92672' },
239
{ tag: tags.string, color: '#e6db74' },
240
{ tag: tags.comment, color: '#75715e' },
241
{ tag: tags.number, color: '#ae81ff' },
242
{ tag: tags.function, color: '#a6e22e' },
243
{ tag: tags.className, color: '#a6e22e' },
244
{ tag: tags.operator, color: '#f92672' },
245
{ tag: tags.variableName, color: '#f8f8f2' },
246
{ tag: tags.typeName, color: '#66d9ef' },
247
{ tag: tags.propertyName, color: '#a6e22e' }
248
]);
249
250
// Combine into complete theme
251
const darkTheme: Extension = [
252
darkEditorTheme,
253
syntaxHighlighting(darkHighlightStyle)
254
];
255
256
// Register custom theme
257
const customTheme: IEditorTheme = {
258
name: 'monokai-dark',
259
displayName: 'Monokai Dark',
260
theme: darkTheme
261
};
262
263
registry.addTheme(customTheme);
264
```
265
266
### JupyterLab CSS Variable Integration
267
268
Using JupyterLab's CSS variables for consistent theming.
269
270
```typescript
271
// Theme using JupyterLab CSS variables
272
const jupyterLabIntegratedTheme = EditorView.theme({
273
'&': {
274
color: 'var(--jp-content-font-color1)',
275
backgroundColor: 'var(--jp-layout-color1)'
276
},
277
'.cm-content': {
278
padding: 'var(--jp-code-padding)',
279
fontFamily: 'var(--jp-code-font-family)',
280
fontSize: 'var(--jp-code-font-size)',
281
lineHeight: 'var(--jp-code-line-height)',
282
caretColor: 'var(--jp-editor-cursor-color)'
283
},
284
'.cm-focused .cm-cursor': {
285
borderLeftColor: 'var(--jp-editor-cursor-color)'
286
},
287
'.cm-focused .cm-selectionBackground, ::selection': {
288
backgroundColor: 'var(--jp-editor-selected-background)'
289
},
290
'.cm-gutters': {
291
backgroundColor: 'var(--jp-layout-color1)',
292
color: 'var(--jp-ui-font-color2)',
293
borderRight: '1px solid var(--jp-border-color2)'
294
},
295
'.cm-activeLineGutter': {
296
backgroundColor: 'var(--jp-layout-color2)'
297
},
298
'.cm-activeLine': {
299
backgroundColor: 'rgba(125, 125, 125, 0.1)'
300
},
301
'.cm-searchMatch': {
302
backgroundColor: 'var(--jp-search-selected-match-background-color)',
303
color: 'var(--jp-search-selected-match-color)'
304
},
305
'.cm-searchMatch.cm-searchMatch-selected': {
306
backgroundColor: 'var(--jp-search-selected-match-background-color)',
307
color: 'var(--jp-search-selected-match-color)'
308
}
309
});
310
311
// Syntax highlighting with CSS variables
312
const jupyterLabHighlightStyle = HighlightStyle.define([
313
{ tag: tags.keyword, color: 'var(--jp-mirror-editor-keyword-color)' },
314
{ tag: tags.string, color: 'var(--jp-mirror-editor-string-color)' },
315
{ tag: tags.comment, color: 'var(--jp-mirror-editor-comment-color)' },
316
{ tag: tags.number, color: 'var(--jp-mirror-editor-number-color)' },
317
{ tag: tags.operator, color: 'var(--jp-mirror-editor-operator-color)' },
318
{ tag: tags.punctuation, color: 'var(--jp-mirror-editor-punctuation-color)' },
319
{ tag: tags.function, color: 'var(--jp-mirror-editor-def-color)' },
320
{ tag: tags.className, color: 'var(--jp-mirror-editor-def-color)' },
321
{ tag: tags.variableName, color: 'var(--jp-mirror-editor-variable-color)' },
322
{ tag: tags.typeName, color: 'var(--jp-mirror-editor-variable-2-color)' },
323
{ tag: tags.propertyName, color: 'var(--jp-mirror-editor-property-color)' },
324
{ tag: tags.attributeName, color: 'var(--jp-mirror-editor-attribute-color)' },
325
{ tag: tags.tagName, color: 'var(--jp-mirror-editor-tag-color)' }
326
]);
327
```
328
329
### Dynamic Theme Switching
330
331
Implementing dynamic theme switching at runtime.
332
333
```typescript
334
class ThemeManager {
335
private registry: EditorThemeRegistry;
336
private currentTheme: string = 'default';
337
private editors: EditorView[] = [];
338
339
constructor(registry: EditorThemeRegistry) {
340
this.registry = registry;
341
}
342
343
// Register editor for theme updates
344
registerEditor(editor: EditorView): void {
345
this.editors.push(editor);
346
}
347
348
// Switch theme for all registered editors
349
setTheme(themeName: string): void {
350
const theme = this.registry.getTheme(themeName);
351
this.currentTheme = themeName;
352
353
this.editors.forEach(editor => {
354
editor.dispatch({
355
effects: StateEffect.reconfigure.of([
356
// Reconfigure with new theme
357
theme,
358
// ... other extensions
359
])
360
});
361
});
362
}
363
364
// Get current theme name
365
getCurrentTheme(): string {
366
return this.currentTheme;
367
}
368
369
// Get available themes
370
getAvailableThemes(): IEditorTheme[] {
371
return this.registry.themes;
372
}
373
}
374
375
// Usage
376
const themeManager = new ThemeManager(registry);
377
378
// Register editors
379
themeManager.registerEditor(editor1);
380
themeManager.registerEditor(editor2);
381
382
// Switch themes
383
themeManager.setTheme('monokai-dark');
384
themeManager.setTheme('jupyter-light');
385
```
386
387
### Responsive Theme System
388
389
Creating themes that respond to system preferences.
390
391
```typescript
392
// Responsive theme that adapts to system dark/light mode
393
const responsiveTheme = EditorView.theme({
394
'&': {
395
color: 'var(--jp-content-font-color1)',
396
backgroundColor: 'var(--jp-layout-color1)'
397
}
398
}, {
399
// Use system preference for dark mode detection
400
dark: window.matchMedia('(prefers-color-scheme: dark)').matches
401
});
402
403
// Listen for system theme changes
404
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
405
const newTheme = e.matches ? 'dark' : 'light';
406
themeManager.setTheme(newTheme);
407
});
408
409
// Auto-theme selection based on JupyterLab theme
410
function getAutoTheme(): string {
411
const body = document.body;
412
const isDark = body.classList.contains('jp-theme-dark') ||
413
body.classList.contains('theme-dark');
414
return isDark ? 'jupyter-dark' : 'jupyter-light';
415
}
416
```
417
418
### Advanced Theme Features
419
420
Complex theming with animations, custom properties, and conditional styling.
421
422
```typescript
423
// Theme with animations and transitions
424
const animatedTheme = EditorView.theme({
425
'.cm-cursor': {
426
transition: 'opacity 0.3s ease-in-out'
427
},
428
'.cm-selectionBackground': {
429
transition: 'background-color 0.2s ease'
430
},
431
'.cm-activeLine': {
432
transition: 'background-color 0.15s ease'
433
},
434
'.cm-focused .cm-matchingBracket': {
435
backgroundColor: 'var(--jp-brand-color0)',
436
animation: 'bracket-highlight 0.3s ease'
437
},
438
'@keyframes bracket-highlight': {
439
'0%': { backgroundColor: 'transparent' },
440
'50%': { backgroundColor: 'var(--jp-brand-color0)' },
441
'100%': { backgroundColor: 'var(--jp-brand-color0)' }
442
}
443
});
444
445
// Conditional theme based on editor state
446
const conditionalTheme = EditorView.theme({
447
'&.cm-editor.cm-readonly': {
448
backgroundColor: 'var(--jp-layout-color2)',
449
opacity: 0.8
450
},
451
'&.cm-editor.cm-has-errors': {
452
borderLeft: '3px solid var(--jp-error-color1)'
453
},
454
'&.cm-editor.cm-collaborative': {
455
borderTop: '2px solid var(--jp-brand-color2)'
456
}
457
});
458
```
459
460
## Types
461
462
```typescript { .api }
463
interface IEditorTheme {
464
readonly name: string;
465
readonly displayName?: string;
466
readonly theme: Extension;
467
}
468
469
interface IEditorThemeRegistry {
470
readonly themes: IEditorTheme[];
471
defaultTheme(): Extension;
472
addTheme(theme: IEditorTheme): void;
473
getTheme(name: string): Extension;
474
}
475
```