Understanding Inkdown’s CSS architecture helps you create better themes and debug styling issues.
Theme Application Flow
Built-in Themes
Built-in themes (default-dark, default-light) are bundled with the application and use CSS classes:
// Simply swap the class on documentElement
document.documentElement.className = 'theme-dark';
// or
document.documentElement.className = 'theme-light';
The CSS for built-in themes is pre-loaded in the app bundle, so switching is instant.
Custom Themes
Custom themes are loaded from disk and injected dynamically:
private applyCustomThemeCSS(cssContent: string): void {
// Remove any existing custom theme
const existing = document.getElementById('inkdown-custom-theme');
if (existing) existing.remove();
// Inject new theme CSS
const styleElement = document.createElement('style');
styleElement.id = 'inkdown-custom-theme';
styleElement.textContent = cssContent;
document.head.appendChild(styleElement);
}
Source: packages/core/src/ThemeManager.ts:76-86
CSS Class Structure
All themes use the .theme-dark or .theme-light class as the root selector:
.theme-dark {
/* All theme variables go here */
--bg-primary: #161618;
--text-primary: #e0e0e0;
/* ... */
}
This allows themes to coexist in the CSS without conflicts.
Variable Inheritance
Inkdown uses a three-layer CSS variable system:
Layer 1: Shared Variables
Font stacks, spacing, and sizing constants that are the same across all themes:
:root,
.theme-dark,
.theme-light {
/* Font Families */
--font-family: system-ui, -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
--font-family-mono: ui-monospace, 'SF Mono', 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, monospace;
/* Base Sizes */
--font-size-base: 14px;
--spacing-md: 16px;
--radius-md: 6px;
/* Heading Sizes */
--heading-h1-size: 2em;
--heading-h2-size: 1.6em;
--heading-h3-size: 1.4em;
}
Source: packages/core/src/styles/themes/_shared.css
Layer 2: Theme-Specific Colors
Each theme defines colors and visual styling:
.theme-dark {
--bg-primary: #161618;
--text-primary: #e0e0e0;
--color-primary: #007acc;
/* ... */
}
.theme-light {
--bg-primary: #ffffff;
--text-primary: #24292f;
--color-primary: #0969da;
/* ... */
}
Sources:
packages/core/src/styles/themes/default-dark.css
packages/core/src/styles/themes/default-light.css
Layer 3: Component Styles
Components reference theme variables:
.editor {
background: var(--editor-bg);
color: var(--editor-fg);
}
.sidebar {
background: var(--bg-sidebar);
border-right: 1px solid var(--border-color);
}
button.primary {
background: var(--button-primary-bg);
color: var(--button-primary-text);
}
CSS Loading Order
- Base styles: Reset, typography, layout
- Shared variables: Constants and sizing
- Built-in theme CSS: Default dark/light themes
- Component styles: UI components and editor
- Custom theme injection (if applicable): Dynamically loaded
This order ensures custom themes can override anything while inheriting shared constants.
Selector Specificity
Themes use a flat specificity hierarchy:
/* Low specificity - easily overridden */
.theme-dark {
--color-primary: #007acc;
}
/* Components reference variables */
.button {
background: var(--color-primary);
}
Avoid deep nesting in themes:
/* ❌ Bad - too specific */
.theme-dark .editor .heading h1 {
color: #569cd6;
}
/* ✅ Good - use variables */
.theme-dark {
--heading-h1: #569cd6;
}
Color Scheme Switching
When switching between light and dark modes:
// For built-in themes: swap CSS class
document.documentElement.className = newScheme === 'dark'
? 'theme-dark'
: 'theme-light';
// For custom themes with both modes: load different CSS file
const cssFile = `${colorScheme}.css`; // 'dark.css' or 'light.css'
const cssContent = await readThemeCss(themeId, cssFile);
applyCustomThemeCSS(cssContent);
Source: packages/core/src/ThemeManager.ts:169-192
Theme Manager Architecture
ThemeManager Responsibilities
- Registering built-in themes
- Loading custom themes from disk
- Applying themes by setting CSS classes or injecting CSS
- Managing color scheme switching
- Persisting theme preferences
- Emitting theme change events
Source: packages/core/src/ThemeManager.ts
Initialization Flow
async init(): Promise<void> {
// 1. Register built-in themes
this.registerBuiltInThemes();
// 2. Load custom themes from disk
await this.loadCustomThemes();
// 3. Load saved preference from config
const config = await this.app.configManager.loadConfig('app');
this.colorScheme = config?.colorScheme || 'dark';
this.currentTheme = config?.theme || 'default-dark';
// 4. Apply the theme
this.applyColorScheme(this.colorScheme);
}
Source: From theme-system.md documentation
CSS variables are extremely fast. The browser handles variable resolution natively:
/* No performance cost */
.element {
color: var(--text-primary);
background: var(--bg-primary);
}
- Built-in themes: Instant (just a class swap)
- Custom themes: ~50ms (read file + inject CSS)
Optimization Tips
- Minimize custom CSS: Only define variables you need to change
- Avoid expensive selectors: Keep selectors simple and flat
- Use variable references:
--cursor: var(--color-primary) instead of duplicating colors
- Batch theme loads: Load all theme metadata at startup, defer CSS loading until needed
File Organization
packages/core/src/styles/
├── themes/
│ ├── _shared.css # Shared constants
│ ├── default-dark.css # Built-in dark theme
│ └── default-light.css # Built-in light theme
├── base.css # Base styles
├── index.css # Main entry point
└── plugin-api.css # Plugin UI styles
~/Library/Application Support/com.furqas.inkdown/themes/
└── my-theme/
├── manifest.json # Theme metadata
├── dark.css # Dark mode styles
├── light.css # Light mode styles (optional)
└── README.md # Documentation (optional)
Best Practices
Always use CSS variables instead of hardcoded colors. This ensures themes can be customized.
Never use !important in themes. It breaks customization and causes specificity wars.
Test with multiple content types including code blocks, tables, callouts, and lists to ensure complete coverage.
Next Steps
CSS Variables
Complete reference of all theme variables
Color Schemes
Light/dark mode implementation