Skip to content

Theming

notectl ships with a complete theming system built on CSS custom properties. Two built-in themes (light & dark), automatic system-preference detection, and full custom-theme support are included out of the box.

For CSP runtime styling and nonce setup, see the Content Security Policy guide.

import { createEditor, ThemePreset } from '@notectl/core';
const editor = await createEditor({
theme: ThemePreset.Dark,
});

Available presets:

PresetValueDescription
ThemePreset.Light'light'Default light theme
ThemePreset.Dark'dark'Dark theme (Catppuccin-inspired)
ThemePreset.System'system'Follows OS prefers-color-scheme
<notectl-editor theme="dark"></notectl-editor>
const editor = await createEditor({
theme: ThemePreset.System,
});

The editor listens to prefers-color-scheme changes and switches automatically.

// Switch to dark
editor.setTheme(ThemePreset.Dark);
// Read current theme
const current = editor.getTheme(); // 'light' | 'dark' | 'system' | Theme object

Toggle example:

const toggle = document.getElementById('theme-toggle');
toggle.addEventListener('click', () => {
const next = editor.getTheme() === ThemePreset.Dark
? ThemePreset.Light
: ThemePreset.Dark;
editor.setTheme(next);
});

Create a custom theme by extending a built-in base theme with partial overrides:

import { createTheme, LIGHT_THEME, createEditor } from '@notectl/core';
import type { Theme } from '@notectl/core';
const corporate: Theme = createTheme(LIGHT_THEME, {
name: 'corporate',
primitives: {
primary: '#6B21A8',
primaryForeground: '#6B21A8',
primaryMuted: 'rgba(107, 33, 168, 0.15)',
borderFocus: '#6B21A8',
focusRing: 'rgba(107, 33, 168, 0.2)',
},
});
const editor = await createEditor({ theme: corporate });

Only specify the values you want to change — everything else falls back to the base theme.

Component-level tokens (toolbar, code block, tooltip) can be overridden independently:

const myTheme: Theme = createTheme(DARK_THEME, {
name: 'custom-dark',
codeBlock: {
background: '#0d1117',
foreground: '#c9d1d9',
},
tooltip: {
background: '#21262d',
foreground: '#f0f6fc',
},
});

Themes are plain objects — export them from a package:

my-theme-package/index.ts
import { createTheme, DARK_THEME } from '@notectl/core';
import type { Theme } from '@notectl/core';
export const OCEAN_THEME: Theme = createTheme(DARK_THEME, {
name: 'ocean',
primitives: {
primary: '#06b6d4',
background: '#0c1222',
surfaceRaised: '#1a2332',
surfaceOverlay: '#1a2332',
},
});

The theme engine sets all properties on :host inside the Shadow DOM. Every color in the editor references these variables.

These are the core tokens that all components derive their colors from.

CSS PropertyTokenDescription
--notectl-bgbackgroundEditor canvas background
--notectl-fgforegroundMain text color
--notectl-fg-mutedmutedForegroundSecondary text (placeholders, labels, arrows)
--notectl-borderborderDefault borders (editor, toolbar, inputs, separators)
--notectl-border-focusborderFocusFocus state border
--notectl-primaryprimaryAccent color (selection outlines, insert lines, active states)
--notectl-primary-fgprimaryForegroundText on primary-tinted backgrounds
--notectl-primary-mutedprimaryMutedSubtle primary background (active toolbar button, selected cells)
--notectl-surface-raisedsurfaceRaisedElevated surfaces (toolbar background)
--notectl-surface-overlaysurfaceOverlayOverlay surfaces (popups, context menus, dropdowns)
--notectl-hover-bghoverBackgroundHover state background
--notectl-active-bgactiveBackgroundActive/pressed state background
--notectl-dangerdangerDelete and error color
--notectl-danger-muteddangerMutedSubtle danger background
--notectl-successsuccessChecked/success color (checklist checkmarks)
--notectl-shadowshadowBox-shadow color
--notectl-focus-ringfocusRingFocus ring shadow (typically semi-transparent)
CSS PropertyTokenFallback
--notectl-toolbar-bgtoolbar.backgroundvar(--notectl-surface-raised)
--notectl-toolbar-bordertoolbar.borderColorvar(--notectl-border)
CSS PropertyTokenFallback
--notectl-code-block-bgcodeBlock.backgroundvar(--notectl-surface-raised)
--notectl-code-block-colorcodeBlock.foregroundvar(--notectl-fg)
--notectl-code-block-header-bgcodeBlock.headerBackgroundvar(--notectl-surface-raised)
--notectl-code-block-header-colorcodeBlock.headerForegroundvar(--notectl-fg-muted)
--notectl-code-block-header-bordercodeBlock.headerBordervar(--notectl-border)

When a ThemeSyntax object is provided in codeBlock.syntax, token colors are emitted as CSS custom properties. Each falls back to var(--notectl-code-block-color) when not set. There are 16 canonical token types; the theme engine derives all variables automatically from the SYNTAX_TOKEN_TYPES list.

CSS PropertyTokenFallback
--notectl-code-token-keywordcodeBlock.syntax.keywordvar(--notectl-code-block-color)
--notectl-code-token-stringcodeBlock.syntax.stringvar(--notectl-code-block-color)
--notectl-code-token-commentcodeBlock.syntax.commentvar(--notectl-code-block-color)
--notectl-code-token-numbercodeBlock.syntax.numbervar(--notectl-code-block-color)
--notectl-code-token-functioncodeBlock.syntax.functionvar(--notectl-code-block-color)
--notectl-code-token-operatorcodeBlock.syntax.operatorvar(--notectl-code-block-color)
--notectl-code-token-punctuationcodeBlock.syntax.punctuationvar(--notectl-code-block-color)
--notectl-code-token-booleancodeBlock.syntax.booleanvar(--notectl-code-block-color)
--notectl-code-token-nullcodeBlock.syntax.nullvar(--notectl-code-block-color)
--notectl-code-token-propertycodeBlock.syntax.propertyvar(--notectl-code-block-color)
--notectl-code-token-typecodeBlock.syntax.typevar(--notectl-code-block-color)
--notectl-code-token-annotationcodeBlock.syntax.annotationvar(--notectl-code-block-color)
--notectl-code-token-tagcodeBlock.syntax.tagvar(--notectl-code-block-color)
--notectl-code-token-attributecodeBlock.syntax.attributevar(--notectl-code-block-color)
--notectl-code-token-constantcodeBlock.syntax.constantvar(--notectl-code-block-color)
--notectl-code-token-regexcodeBlock.syntax.regexvar(--notectl-code-block-color)

Tokens whose value is a TokenStyle object (rather than a plain color string) additionally emit font-style and font-weight variables when those fields are set:

CSS Property patternEmitted when
--notectl-code-token-<type>-font-styleTokenStyle.fontStyle is set
--notectl-code-token-<type>-font-weightTokenStyle.fontWeight is set

The built-in light and dark themes both include full syntax token definitions for all 16 types, so code blocks are styled automatically.

CSS PropertyTokenFallback
--notectl-tooltip-bgtooltip.backgroundvar(--notectl-fg)
--notectl-tooltip-fgtooltip.foregroundvar(--notectl-bg)
CSS PropertyDescription
--notectl-content-min-heightMinimum height of the content area (default: 400px)

When a syntax highlighter is configured on the CodeBlockPlugin, token classes are applied to code content. The built-in light and dark themes include full syntax color definitions via CSS custom properties (see the Code Block Syntax Tokens reference above), so code blocks are styled automatically.

To customize syntax colors, override the codeBlock.syntax section in your custom theme. Each token accepts either a plain color string or a TokenStyle object for full font-weight and font-style control:

import { createTheme, LIGHT_THEME } from '@notectl/core';
import type { Theme } from '@notectl/core';
const myTheme: Theme = createTheme(LIGHT_THEME, {
name: 'custom-syntax',
codeBlock: {
syntax: {
keyword: '#d73a49',
string: '#032f62',
number: '#005cc5',
comment: { color: '#6a737d', fontStyle: 'italic' },
function: '#6f42c1',
operator: '#d73a49',
punctuation: '#24292e',
boolean: '#005cc5',
null: '#005cc5',
property: '#005cc5',
// New in 16-token system:
type: '#e36209',
annotation: '#6f42c1',
tag: '#22863a',
attribute: '#6f42c1',
constant: '#005cc5',
regex: '#032f62',
},
},
});

You only need to specify tokens you want to override — unspecified tokens inherit from the base theme.

interface ThemePrimitives {
readonly background: string;
readonly foreground: string;
readonly mutedForeground: string;
readonly border: string;
readonly borderFocus: string;
readonly primary: string;
readonly primaryForeground: string;
readonly primaryMuted: string;
readonly surfaceRaised: string;
readonly surfaceOverlay: string;
readonly hoverBackground: string;
readonly activeBackground: string;
readonly danger: string;
readonly dangerMuted: string;
readonly success: string;
readonly shadow: string;
readonly focusRing: string;
}
/** Per-token style: color plus optional font weight and style. */
interface TokenStyle {
readonly color: string;
readonly fontWeight?: 'normal' | 'bold';
readonly fontStyle?: 'normal' | 'italic';
}
/** A token style value is either a plain color string or a full TokenStyle object. */
type TokenStyleValue = string | TokenStyle;
/**
* Syntax highlighting styles for all 16 canonical token types.
* Derived automatically from SYNTAX_TOKEN_TYPES — adding a new token type
* here propagates to CSS variables and theme validation.
*/
type ThemeSyntax = { readonly [K in SyntaxTokenType]: TokenStyleValue };
// The 16 canonical token types:
// 'keyword' | 'string' | 'comment' | 'number' | 'function' | 'operator'
// | 'punctuation' | 'boolean' | 'null' | 'property'
// | 'type' | 'annotation' | 'tag' | 'attribute' | 'constant' | 'regex'
interface ThemeCodeBlock {
readonly background: string;
readonly foreground: string;
readonly headerBackground: string;
readonly headerForeground: string;
readonly headerBorder: string;
readonly syntax?: ThemeSyntax;
}
interface Theme {
readonly name: string;
readonly primitives: ThemePrimitives;
readonly toolbar?: Partial<ThemeToolbar>;
readonly codeBlock?: Partial<ThemeCodeBlock>;
readonly tooltip?: Partial<ThemeTooltip>;
}

All theme-related exports from @notectl/core:

ExportKindDescription
ThemePresetEnum objectLight, Dark, System
LIGHT_THEMEConstantBuilt-in light theme
DARK_THEMEConstantBuilt-in dark theme
SYNTAX_TOKEN_TYPESConstantTuple of all 16 canonical token type names
createTheme(base, overrides)FunctionCreate custom theme from a base
resolveTheme(preset | theme)FunctionResolve a preset to a full Theme
generateThemeCSS(theme)FunctionGenerate CSS string from a Theme
createThemeStyleSheet(theme)FunctionCreate a CSSStyleSheet from a Theme
ThemeTypeFull theme definition
PartialThemeTypePartial overrides for createTheme()
ThemePrimitivesTypePrimitive color palette
ThemeToolbarTypeToolbar color overrides
ThemeCodeBlockTypeCode block color overrides (includes syntax)
ThemeSyntaxTypeSyntax token styles — mapped type over all 16 token types
ThemeTooltipTypeTooltip color overrides
SyntaxTokenTypeTypeUnion of all 16 token type name strings
TokenStyleTypePer-token style with color, optional fontWeight and fontStyle
TokenStyleValueTypestring | TokenStyle — accepted by every syntax token slot

Plugins that create UI elements (popups, dialogs, overlays) should reference theme variables instead of hardcoding colors. Use context.registerStyleSheet() to inject CSS that references the theme custom properties:

// Register a stylesheet during plugin init()
context.registerStyleSheet(`
.my-popup {
background: var(--notectl-surface-overlay);
border: 1px solid var(--notectl-border);
color: var(--notectl-fg);
box-shadow: 0 4px 12px var(--notectl-shadow);
}
`);

This ensures your plugin adapts automatically when the user switches themes, and remains CSP-compliant since all styles are injected via adopted stylesheets rather than inline style attributes.