Themes
Extensions can contribute custom color themes that control the appearance of the entire application, the code editor, and syntax highlighting.
Overview
A theme contribution defines a complete visual style for the app. Each theme targets three distinct layers:
- App colors -- the chrome surrounding the editor (backgrounds, surfaces, borders, text, status indicators).
- Editor colors -- the CodeMirror editor pane (editor background, foreground, caret, selection, gutter).
- Token colors -- syntax highlighting for code (keywords, strings, comments, types, and more).
Themes are declared in your extension's manifest.json under the contributes.themes array. They are registered automatically at extension load time and appear in the built-in theme picker alongside the default Dark and Light themes.
Theme structure
Every theme contribution must include these required fields:
| Field | Type | Description |
|---|---|---|
id | String | A unique identifier for the theme (e.g. "my-dark-theme"). |
label | String | The display name shown in the theme picker (e.g. "My Dark Theme"). |
type | String | Must be "dark" or "light". Determines the base brightness and default fallback colors. Any other value causes a validation error. |
And these optional color maps:
| Field | Type | Description |
|---|---|---|
appColors | Map<String, String>? | Hex color overrides for app-level UI elements. |
editorColors | Map<String, String>? | Hex color overrides for the CodeMirror editor pane. |
tokenColors | Map<String, String>? | Hex color overrides for syntax token highlighting. |
The extensionId field is set automatically from the extension manifest -- you do not declare it.
All color values are CSS hex strings (#rrggbb or #rrggbbaa). Shorthand three-character hex (#rgb) is also supported.
App colors
The appColors map controls the application shell: navigation, panels, dialogs, buttons, and status indicators. Every key is optional -- omitted keys fall back to the built-in defaults for the theme's type (dark or light).
Available keys
Layout
| Key | Description | Dark default | Light default |
|---|---|---|---|
background | Main background color | #1e1e1e | #ffffff |
surface | Elevated surfaces (cards, panels, dialogs) | #252526 | #f3f3f3 |
border | Borders and dividers | #333333 | #d4d4d4 |
Accent
| Key | Description | Dark default | Light default |
|---|---|---|---|
primary | Primary accent (buttons, links, active tabs) | #569cd6 | #0066b8 |
secondary | Secondary accent | #4ec9b0 | #267f99 |
accent | Tertiary accent | #c586c0 | #af00db |
positive | Positive/affirmative accent | #6a9955 | #008000 |
highlight | Highlight accent | #d7ba7d | #795e26 |
warm | Warm accent | #ce9178 | #a31515 |
Text
| Key | Description | Dark default | Light default |
|---|---|---|---|
text | Primary text | #d4d4d4 | #333333 |
textMuted | Secondary/muted text | #9e9e9e | #616161 |
textFaint | Disabled/faint text | #6d6d6d | #888888 |
Status
| Key | Description | Dark default | Light default |
|---|---|---|---|
error | Error indicators | #f14c4c | #e51400 |
warning | Warning indicators | #cca700 | #bf8803 |
success | Success indicators | #89d185 | #388a34 |
Editor (via appColors)
| Key | Description | Dark default | Light default |
|---|---|---|---|
selection | Text selection highlight | #2f8cea84 | #2f8cea84 |
cursor | Cursor/caret color | #aeafad | #000000 |
lineNumber | Line number gutter text | #858585 | #237893 |
Example
{
"appColors": {
"background": "#1a1a2e",
"surface": "#16213e",
"primary": "#e94560",
"text": "#eaeaea",
"textMuted": "#8888aa",
"error": "#ff6b6b"
}
}
Editor colors
The editorColors map targets the CodeMirror code editor pane specifically. These values are passed directly to the CodeMirror createTheme() configuration as the settings block.
Available keys
| Key | Description |
|---|---|
background | Editor background |
foreground | Default text color in the editor |
caret | Cursor/caret color |
selection | Selected text highlight |
gutterForeground | Line number color in the gutter |
Cross-layer derivation
If you provide only appColors without editorColors, the editor colors are automatically derived from the corresponding app color keys:
| Editor key | Derived from app key |
|---|---|
background | background |
foreground | text |
caret | cursor |
selection | selection |
gutterForeground | lineNumber |
Conversely, if you provide only editorColors without appColors, certain app colors are derived from the editor colors:
| App key | Derived from editor key |
|---|---|
background | background |
text | foreground |
cursor | caret |
selection | selection |
lineNumber | gutterForeground |
If both maps are provided, each is applied independently with no derivation.
Example
{
"editorColors": {
"background": "#0f0f1a",
"foreground": "#d4d4d4",
"caret": "#e94560",
"selection": "#e9456033",
"gutterForeground": "#4a4a6a"
}
}
Token colors
The tokenColors map controls syntax highlighting in the code editor. Each key corresponds to a CodeMirror syntax tag. Omitted keys fall back to brightness-based defaults.
Available keys
| Key | Description | Dark default | Light default |
|---|---|---|---|
keyword | Language keywords (if, return, class) | #569cd6 | #0000ff |
string | String literals | #d7ba7d | #a31515 |
comment | Comments | #6a9955 | #008000 |
number | Numeric literals | #b5cea8 | #098658 |
typeName | Type names and annotations | #4ec9b0 | #267f99 |
function | Function and method names | #dcdcaa | #795e26 |
variableName | Variable names | #9cdcfe | #001080 |
special | Special tokens (decorators, macros) | #c586c0 | #af00db |
Example
{
"tokenColors": {
"keyword": "#e94560",
"string": "#0f3460",
"comment": "#4a4a6a",
"number": "#e94560",
"typeName": "#4ec9b0",
"function": "#dcdcaa",
"variableName": "#9cdcfe",
"special": "#c586c0"
}
}
Dark vs. light themes
The type field determines the base brightness of your theme and has two important effects:
- Fallback colors. Any color key you omit from
appColors,editorColors, ortokenColorsis filled from the built-in dark or light defaults listed in the tables above. - System integration. The brightness value propagates to the Flutter
ThemeData, which affects system UI elements such as the status bar style, keyboard appearance, and dialog defaults.
The type field is validated at load time. If the value is anything other than "dark" or "light", the theme is rejected and not registered. An error is logged to the extension console.
Manifest example
Below is a complete manifest.json for a theme extension that contributes both a dark and a light variant:
{
"id": "ocean-themes",
"name": "Ocean Themes",
"version": "1.0.0",
"description": "Dark and light ocean-inspired color themes",
"author": "Theme Author",
"contributes": {
"themes": [
{
"id": "ocean-dark",
"label": "Ocean Dark",
"type": "dark",
"appColors": {
"background": "#1a1a2e",
"surface": "#16213e",
"border": "#0f3460",
"primary": "#e94560",
"secondary": "#4ec9b0",
"text": "#eaeaea",
"textMuted": "#8888aa",
"textFaint": "#555577"
},
"editorColors": {
"background": "#0f0f1a",
"foreground": "#d4d4d4",
"caret": "#e94560",
"selection": "#e9456033",
"gutterForeground": "#4a4a6a"
},
"tokenColors": {
"keyword": "#e94560",
"string": "#ce9178",
"comment": "#4a4a6a",
"number": "#b5cea8",
"typeName": "#4ec9b0",
"function": "#dcdcaa",
"variableName": "#9cdcfe",
"special": "#c586c0"
}
},
{
"id": "ocean-light",
"label": "Ocean Light",
"type": "light",
"appColors": {
"background": "#f0f5ff",
"surface": "#e0eaff",
"border": "#b0c4de",
"primary": "#0066b8",
"text": "#1a1a2e"
},
"tokenColors": {
"keyword": "#0000ff",
"string": "#a31515",
"comment": "#008000"
}
}
]
}
}
A single extension can contribute multiple themes. Each theme entry in the array is registered independently.
Minimal theme example
You do not need to specify all three color maps. A theme can override only what it needs. For example, a theme that only customizes syntax highlighting:
{
"id": "syntax-only-theme",
"name": "Custom Syntax",
"version": "1.0.0",
"contributes": {
"themes": [
{
"id": "custom-syntax",
"label": "Custom Syntax Colors",
"type": "dark",
"tokenColors": {
"keyword": "#ff79c6",
"string": "#f1fa8c",
"comment": "#6272a4"
}
}
]
}
}
All app and editor colors will use the built-in dark defaults.
Reading the current theme
Extensions can read the currently active theme and listen for theme changes at runtime. This is useful for extensions that render custom UI (popups, webviews, bottom sheets) and need to match the app's color scheme.
Get current theme
import editor from "@srcnexus/ext-sdk";
const theme = await editor.workspace.getTheme();
console.log(theme.id); // "dark"
console.log(theme.type); // "dark" or "light"
console.log(theme.appColors.background); // "#1e1e1e"
console.log(theme.editorColors.caret); // "#aeafad"
console.log(theme.tokenColors.keyword); // "#569cd6"
The returned object contains the full resolved theme with all three color layers:
| Field | Type | Description |
|---|---|---|
id | string | Theme identifier (e.g. "dark", "ocean-dark") |
label | string | Display name (e.g. "Dark (Default)") |
type | "dark" | "light" | Theme brightness |
appColors | object | All app UI colors as hex strings (see App colors) |
editorColors | object | Editor pane colors as hex strings (see Editor colors) |
tokenColors | object | Syntax token colors as hex strings (see Token colors) |
Listen for theme changes
import editor from "@srcnexus/ext-sdk";
const unsubscribe = editor.events.onThemeChange((theme) => {
// theme has the same shape as workspace.getTheme() result
console.log("Theme changed to:", theme.label);
updateMyUI(theme.appColors);
});
// Later, to stop listening:
unsubscribe();
The onThemeChange event fires whenever the user switches to a different theme. The handler receives the full theme object, identical in shape to what workspace.getTheme() returns.
No special permission is required to read theme data.
Theme picker API
Extensions can programmatically open the theme picker using the window.showThemePicker RPC method. This opens the same theme selection dialog available via the command palette.
SDK usage
await editor.window.showThemePicker();
No parameters are required. The method opens a picker dialog listing all available themes (built-in and extension-contributed) with the currently active theme pre-selected. The user selects a theme and the selection is applied immediately and persisted to the workbench.theme setting.
The method returns when the user makes a selection or dismisses the dialog.
Theme selection command
The theme picker can also be triggered via the theme.select command:
// Open the picker dialog
await rpc.call("commands.execute", { id: "theme.select" });
// Or apply a specific theme directly by ID
await rpc.call("commands.execute", { id: "theme.select", args: "ocean-dark" });
When a string argument is passed, the theme is applied directly without showing the picker.
Terminal theme integration
Theme colors are also applied to the integrated terminal. The terminal theme is automatically derived from the app color tokens:
| Terminal element | Derived from |
|---|---|
| Foreground | text |
| Background | background |
| Cursor | cursor |
| Selection | selection |
| Red | error |
| Green | positive |
| Yellow | warning |
| Blue | primary |
| Magenta | accent |
| Cyan | secondary |
Bright ANSI color variants are computed automatically by adjusting the lightness or saturation of the base color.
Theme persistence
When the user selects a theme (whether built-in or extension-contributed), the theme ID is stored in the workbench.theme setting. On the next app launch, the persisted theme is reapplied after all extension themes have been registered. If a previously selected extension theme is no longer available (the extension was uninstalled), the app falls back to the default Dark theme.
Lifecycle
- The extension host parses the
contributes.themesarray frommanifest.json. - Each theme entry is validated (the
typefield must be"dark"or"light"). - Valid themes are merged with the built-in base colors and registered with the editor.
- Themes become available in the theme picker.
- After all extensions finish bootstrapping, the persisted theme preference is reapplied.
- When an extension is uninstalled or disabled, its themes are unregistered. If the active theme belonged to that extension, the app reverts to the Dark theme.