Files
Trilium/docs/Developer Guide/Plugin Development/Theme Development Guide.md
2025-08-21 15:55:44 +00:00

1169 lines
28 KiB
Markdown
Vendored

# Theme Development Guide
This guide covers creating custom themes for Trilium Notes. Themes allow you to customize the visual appearance of the application to match your preferences or organizational branding.
## Prerequisites
- CSS and CSS Variables knowledge
- Understanding of color theory and accessibility
- Basic knowledge of Trilium's note system
- Familiarity with browser developer tools
## Understanding Trilium's Theme System
### Theme Architecture
Trilium's theming system uses CSS custom properties (variables) for consistent styling across the application. Themes are stored as CSS code notes that override default variables.
### Default Themes
Trilium includes several built-in themes:
- **Light** - Default light theme
- **Dark** - Dark mode theme
- **Steel Blue** - Professional blue theme
- **Next** - Modern, clean theme
### CSS Variable Structure
Themes work by overriding CSS custom properties defined in the root scope:
```css
:root {
/* Main colors */
--main-background-color: #ffffff;
--main-text-color: #333333;
--main-border-color: #dddddd;
/* Accent colors */
--primary-color: #007bff;
--secondary-color: #6c757d;
/* Component colors */
--button-background-color: #f0f0f0;
--button-text-color: #333333;
/* And many more... */
}
```
## Creating Your First Theme
### Step 1: Create Theme Note
1. Create a new code note
2. Set type to "CSS"
3. Add label `#appTheme=myThemeName`
4. Add label `#appCss` (for global application)
### Step 2: Define Basic Colors
```css
/* My Custom Theme */
:root {
/* Primary Palette */
--theme-primary: #5e72e4;
--theme-secondary: #825ee4;
--theme-success: #2dce89;
--theme-info: #11cdef;
--theme-warning: #fb6340;
--theme-danger: #f5365c;
/* Neutral Colors */
--theme-white: #ffffff;
--theme-light: #f6f9fc;
--theme-gray: #8898aa;
--theme-dark: #32325d;
--theme-black: #000000;
/* Main Application Colors */
--main-background-color: var(--theme-light);
--main-text-color: var(--theme-dark);
--main-border-color: #e9ecef;
/* Primary Actions */
--primary-color: var(--theme-primary);
--primary-color-hover: #4c63d2;
--primary-text-color: var(--theme-white);
/* Menu and Navigation */
--menu-background-color: var(--theme-white);
--menu-text-color: var(--theme-dark);
--menu-hover-background-color: var(--theme-light);
/* Tree Component */
--tree-background-color: var(--theme-white);
--tree-text-color: var(--theme-dark);
--tree-hover-background-color: #f0f4f8;
--tree-selected-background-color: var(--theme-primary);
--tree-selected-text-color: var(--theme-white);
/* Editor */
--editor-background-color: var(--theme-white);
--editor-text-color: var(--theme-dark);
--editor-selection-background: rgba(94, 114, 228, 0.2);
/* Buttons */
--button-background-color: var(--theme-white);
--button-text-color: var(--theme-dark);
--button-border-color: var(--main-border-color);
--button-hover-background-color: var(--theme-light);
/* Inputs */
--input-background-color: var(--theme-white);
--input-text-color: var(--theme-dark);
--input-border-color: #cbd5e0;
--input-focus-border-color: var(--theme-primary);
/* Code */
--code-background-color: #f7fafc;
--code-text-color: #d63384;
/* Links */
--link-color: var(--theme-primary);
--link-hover-color: var(--theme-secondary);
/* Shadows */
--box-shadow-sm: 0 1px 3px rgba(50, 50, 93, 0.15);
--box-shadow-md: 0 4px 6px rgba(50, 50, 93, 0.11);
--box-shadow-lg: 0 10px 40px rgba(50, 50, 93, 0.2);
}
```
### Step 3: Style Components
```css
/* Component Customizations */
/* Note Tree Styling */
.tree-wrapper {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 1px;
}
.tree {
background: var(--tree-background-color);
border-radius: 8px;
margin: 1px;
}
.fancytree-node {
border-radius: 4px;
margin: 2px 4px;
transition: all 0.2s ease;
}
.fancytree-node:hover {
transform: translateX(2px);
box-shadow: var(--box-shadow-sm);
}
/* Tab Styling */
.note-tab {
background: var(--theme-white);
border: none;
border-radius: 8px 8px 0 0;
margin-right: 2px;
transition: all 0.2s ease;
}
.note-tab:hover {
background: var(--theme-light);
transform: translateY(-2px);
}
.note-tab.active {
background: var(--theme-primary);
color: var(--theme-white);
box-shadow: var(--box-shadow-md);
}
/* Button Styling */
.btn {
border-radius: 6px;
padding: 8px 16px;
font-weight: 500;
transition: all 0.2s ease;
border: 1px solid transparent;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: var(--box-shadow-md);
}
.btn-primary {
background: linear-gradient(135deg, var(--theme-primary) 0%, var(--theme-secondary) 100%);
color: var(--theme-white);
border: none;
}
/* Card/Panel Styling */
.card {
border: none;
border-radius: 12px;
box-shadow: var(--box-shadow-sm);
overflow: hidden;
}
.card-header {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
border-bottom: 2px solid var(--main-border-color);
padding: 12px 16px;
}
/* Dialog Styling */
.modal-content {
border-radius: 12px;
border: none;
box-shadow: var(--box-shadow-lg);
}
.modal-header {
background: linear-gradient(135deg, var(--theme-primary) 0%, var(--theme-secondary) 100%);
color: var(--theme-white);
border-radius: 12px 12px 0 0;
}
/* Scrollbar Styling */
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background: var(--theme-light);
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: linear-gradient(135deg, var(--theme-primary) 0%, var(--theme-secondary) 100%);
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--theme-secondary);
}
```
## Complete Theme Example: Modern Dark
Here's a complete example of a modern dark theme:
```css
/* Modern Dark Theme for Trilium */
:root {
/* Color Palette */
--md-bg-primary: #0f0f23;
--md-bg-secondary: #1a1b3a;
--md-bg-tertiary: #252647;
--md-bg-elevated: #2d2f54;
--md-text-primary: #e4e4e7;
--md-text-secondary: #a1a1aa;
--md-text-muted: #71717a;
--md-accent-primary: #7c3aed;
--md-accent-secondary: #a855f7;
--md-accent-success: #10b981;
--md-accent-warning: #f59e0b;
--md-accent-danger: #ef4444;
--md-accent-info: #3b82f6;
--md-border: rgba(255, 255, 255, 0.1);
--md-border-strong: rgba(255, 255, 255, 0.2);
/* Main Application */
--main-background-color: var(--md-bg-primary);
--main-text-color: var(--md-text-primary);
--main-border-color: var(--md-border);
--body-background-color: var(--md-bg-primary);
/* Accents */
--primary-color: var(--md-accent-primary);
--primary-color-hover: var(--md-accent-secondary);
--primary-text-color: #ffffff;
--muted-text-color: var(--md-text-muted);
--active-item-background-color: var(--md-bg-elevated);
--active-item-text-color: var(--md-accent-primary);
/* Menu */
--menu-background-color: var(--md-bg-secondary);
--menu-text-color: var(--md-text-primary);
--menu-hover-background-color: var(--md-bg-tertiary);
--dropdown-background-color: var(--md-bg-tertiary);
--dropdown-item-hover-background-color: var(--md-bg-elevated);
/* Tree */
--tree-background-color: var(--md-bg-secondary);
--tree-text-color: var(--md-text-primary);
--tree-hover-background-color: var(--md-bg-tertiary);
--tree-selected-background-color: var(--md-accent-primary);
--tree-selected-text-color: #ffffff;
/* Editor */
--editor-background-color: var(--md-bg-primary);
--editor-text-color: var(--md-text-primary);
--editor-selection-background: rgba(124, 58, 237, 0.3);
/* Note Detail */
--detail-background-color: var(--md-bg-primary);
--detail-text-color: var(--md-text-primary);
--accented-background-color: var(--md-bg-secondary);
/* Buttons */
--button-background-color: var(--md-bg-tertiary);
--button-text-color: var(--md-text-primary);
--button-border-color: var(--md-border);
--button-hover-background-color: var(--md-bg-elevated);
--button-disabled-background-color: var(--md-bg-secondary);
/* Inputs */
--input-background-color: var(--md-bg-secondary);
--input-text-color: var(--md-text-primary);
--input-border-color: var(--md-border);
--input-focus-border-color: var(--md-accent-primary);
/* Modal */
--modal-background-color: var(--md-bg-secondary);
--modal-header-background-color: var(--md-bg-tertiary);
--modal-backdrop-color: rgba(0, 0, 0, 0.7);
/* Tabs */
--tab-background-color: var(--md-bg-secondary);
--tab-hover-background-color: var(--md-bg-tertiary);
--tab-active-background-color: var(--md-bg-primary);
--tab-active-text-color: var(--md-accent-primary);
/* Code */
--code-background-color: var(--md-bg-secondary);
--code-text-color: var(--md-accent-info);
/* Links */
--link-color: var(--md-accent-info);
--link-hover-color: var(--md-accent-primary);
/* Tooltips */
--tooltip-background-color: var(--md-bg-elevated);
--tooltip-text-color: var(--md-text-primary);
/* Scrollbar */
--scrollbar-track-color: var(--md-bg-secondary);
--scrollbar-thumb-color: var(--md-bg-elevated);
/* Shadows */
--box-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3);
--box-shadow-md: 0 4px 6px rgba(0, 0, 0, 0.4);
--box-shadow-lg: 0 10px 40px rgba(0, 0, 0, 0.5);
}
/* Component Overrides */
/* Smooth transitions */
* {
transition: background-color 0.2s ease, color 0.2s ease;
}
/* Body and main containers */
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: var(--md-bg-primary);
}
/* Glass morphism effect for cards */
.card {
background: rgba(26, 27, 58, 0.6);
backdrop-filter: blur(10px);
border: 1px solid var(--md-border);
border-radius: 12px;
box-shadow: var(--box-shadow-md);
}
.card-header {
background: rgba(37, 38, 71, 0.8);
border-bottom: 1px solid var(--md-border-strong);
}
/* Gradient accents */
.note-tab.active::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg, var(--md-accent-primary), var(--md-accent-secondary));
}
/* Glow effects for focused elements */
input:focus,
textarea:focus,
.CodeMirror-focused {
outline: none;
border-color: var(--md-accent-primary);
box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.1);
}
/* Button animations */
.btn {
position: relative;
overflow: hidden;
border-radius: 8px;
font-weight: 500;
letter-spacing: 0.025em;
}
.btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
transition: left 0.5s;
}
.btn:hover::before {
left: 100%;
}
.btn-primary {
background: linear-gradient(135deg, var(--md-accent-primary), var(--md-accent-secondary));
border: none;
color: white;
}
/* Tree styling with hover effects */
.fancytree-node {
border-radius: 6px;
padding: 2px 4px;
margin: 2px 8px;
}
.fancytree-node:hover {
background: var(--md-bg-tertiary);
box-shadow: inset 0 0 0 1px var(--md-border-strong);
}
.fancytree-active {
background: var(--md-accent-primary) !important;
color: white !important;
}
.fancytree-active .fancytree-title {
color: white !important;
}
/* Note content styling */
.note-detail {
padding: 24px;
}
.note-detail h1 {
color: var(--md-accent-primary);
border-bottom: 2px solid var(--md-border-strong);
padding-bottom: 12px;
margin-bottom: 24px;
}
.note-detail h2 {
color: var(--md-accent-secondary);
margin-top: 32px;
margin-bottom: 16px;
}
.note-detail code {
background: var(--md-bg-tertiary);
color: var(--md-accent-info);
padding: 2px 6px;
border-radius: 4px;
font-family: 'JetBrains Mono', 'Fira Code', monospace;
}
.note-detail pre {
background: var(--md-bg-secondary);
border: 1px solid var(--md-border);
border-radius: 8px;
padding: 16px;
overflow-x: auto;
}
.note-detail blockquote {
border-left: 4px solid var(--md-accent-primary);
background: var(--md-bg-secondary);
padding: 12px 20px;
margin: 16px 0;
border-radius: 0 8px 8px 0;
}
/* Table styling */
table {
border-collapse: separate;
border-spacing: 0;
border-radius: 8px;
overflow: hidden;
box-shadow: var(--box-shadow-sm);
}
th {
background: var(--md-bg-tertiary);
color: var(--md-accent-primary);
font-weight: 600;
text-transform: uppercase;
font-size: 0.875em;
letter-spacing: 0.05em;
}
td {
background: var(--md-bg-secondary);
border-top: 1px solid var(--md-border);
}
tr:hover td {
background: var(--md-bg-tertiary);
}
/* Scrollbar styling */
::-webkit-scrollbar {
width: 12px;
height: 12px;
}
::-webkit-scrollbar-track {
background: var(--md-bg-secondary);
border-radius: 6px;
}
::-webkit-scrollbar-thumb {
background: var(--md-bg-elevated);
border-radius: 6px;
border: 2px solid var(--md-bg-secondary);
}
::-webkit-scrollbar-thumb:hover {
background: var(--md-accent-primary);
}
/* Animations */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.modal {
animation: fadeIn 0.2s ease;
}
/* Loading states */
.loading {
position: relative;
}
.loading::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(90deg,
transparent,
rgba(124, 58, 237, 0.1),
transparent
);
animation: shimmer 2s infinite;
}
@keyframes shimmer {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
}
/* Focus visible for accessibility */
*:focus-visible {
outline: 2px solid var(--md-accent-primary);
outline-offset: 2px;
}
/* Custom tooltip styling */
.tooltip {
background: var(--md-bg-elevated);
border: 1px solid var(--md-border-strong);
border-radius: 6px;
padding: 8px 12px;
font-size: 0.875em;
box-shadow: var(--box-shadow-lg);
}
.tooltip-arrow {
border-color: var(--md-bg-elevated);
}
```
## Advanced Theming Techniques
### Dynamic Theme Switching
```javascript
// Frontend script for theme switching
class ThemeSwitcher {
constructor() {
this.themes = ['light', 'dark', 'modern-dark', 'custom'];
this.currentTheme = this.getCurrentTheme();
this.init();
}
init() {
this.createThemeToggle();
this.bindEvents();
this.applyTheme(this.currentTheme);
}
createThemeToggle() {
const $toggle = $(`
<div class="theme-switcher">
<button class="theme-toggle" title="Switch Theme">
<span class="bx bx-palette"></span>
</button>
<div class="theme-dropdown">
${this.themes.map(theme => `
<div class="theme-option" data-theme="${theme}">
<span class="theme-preview ${theme}"></span>
<span class="theme-name">${this.formatThemeName(theme)}</span>
</div>
`).join('')}
</div>
</div>
`);
$('body').append($toggle);
// Add styles
$('<style>').text(`
.theme-switcher {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 10000;
}
.theme-toggle {
width: 50px;
height: 50px;
border-radius: 50%;
background: var(--primary-color);
color: white;
border: none;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
cursor: pointer;
font-size: 20px;
}
.theme-dropdown {
position: absolute;
bottom: 60px;
right: 0;
background: var(--menu-background-color);
border: 1px solid var(--main-border-color);
border-radius: 8px;
padding: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
display: none;
min-width: 200px;
}
.theme-switcher.active .theme-dropdown {
display: block;
}
.theme-option {
display: flex;
align-items: center;
padding: 8px;
border-radius: 4px;
cursor: pointer;
gap: 12px;
}
.theme-option:hover {
background: var(--menu-hover-background-color);
}
.theme-preview {
width: 24px;
height: 24px;
border-radius: 50%;
border: 2px solid var(--main-border-color);
}
.theme-preview.light {
background: linear-gradient(135deg, #ffffff, #f0f0f0);
}
.theme-preview.dark {
background: linear-gradient(135deg, #1a1a1a, #2d2d2d);
}
.theme-preview.modern-dark {
background: linear-gradient(135deg, #0f0f23, #7c3aed);
}
.theme-preview.custom {
background: linear-gradient(135deg, #667eea, #764ba2);
}
`).appendTo('head');
}
bindEvents() {
$('.theme-toggle').on('click', () => {
$('.theme-switcher').toggleClass('active');
});
$('.theme-option').on('click', (e) => {
const theme = $(e.currentTarget).data('theme');
this.applyTheme(theme);
$('.theme-switcher').removeClass('active');
});
// Close on outside click
$(document).on('click', (e) => {
if (!$(e.target).closest('.theme-switcher').length) {
$('.theme-switcher').removeClass('active');
}
});
}
async applyTheme(themeName) {
// Save preference
await api.setOption('theme', themeName);
// Apply theme class
$('body').removeClass(this.themes.map(t => `theme-${t}`).join(' '));
$('body').addClass(`theme-${themeName}`);
this.currentTheme = themeName;
// Show notification
await api.showMessage(`Theme changed to ${this.formatThemeName(themeName)}`);
}
getCurrentTheme() {
return api.getOption('theme') || 'light';
}
formatThemeName(theme) {
return theme.split('-').map(word =>
word.charAt(0).toUpperCase() + word.slice(1)
).join(' ');
}
}
// Initialize theme switcher
new ThemeSwitcher();
```
### Responsive Theme Variables
```css
/* Responsive theme adjustments */
@media (max-width: 768px) {
:root {
/* Adjust spacing for mobile */
--mobile-padding: 12px;
--mobile-margin: 8px;
/* Larger touch targets */
--button-min-height: 44px;
--input-min-height: 44px;
}
.btn {
min-height: var(--button-min-height);
padding: 12px 16px;
}
input,
textarea,
select {
min-height: var(--input-min-height);
font-size: 16px; /* Prevent zoom on iOS */
}
.tree {
font-size: 14px;
}
.note-detail {
padding: var(--mobile-padding);
}
}
/* High contrast mode support */
@media (prefers-contrast: high) {
:root {
--main-border-color: #000000;
--button-border-width: 2px;
--focus-outline-width: 3px;
}
.btn {
border: var(--button-border-width) solid var(--button-border-color);
}
*:focus {
outline: var(--focus-outline-width) solid var(--primary-color);
}
}
/* Dark mode preference */
@media (prefers-color-scheme: dark) {
:root:not(.theme-light) {
/* Automatically use dark colors */
--main-background-color: #1a1a1a;
--main-text-color: #e0e0e0;
/* ... other dark mode variables */
}
}
/* Reduced motion support */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
```
### Theme-Specific Icons
```css
/* Custom icon styling per theme */
.theme-modern-dark .bx {
/* Add glow effect to icons */
filter: drop-shadow(0 0 2px rgba(124, 58, 237, 0.5));
}
.theme-modern-dark .bx-star {
color: gold;
filter: drop-shadow(0 0 4px gold);
}
/* Replace icons with theme-specific versions */
.theme-cyberpunk .bx-folder::before {
content: '\eb1f'; /* Different icon */
color: #00ff00;
}
/* Animated icons */
@keyframes pulse {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
}
.theme-animated .bx-bell {
animation: pulse 2s infinite;
}
.theme-animated .note-modified .bx {
animation: pulse 1s;
}
```
## Accessibility Considerations
```css
/* Ensure sufficient color contrast */
:root {
/* WCAG AAA compliant contrast ratios */
--text-on-primary: #ffffff; /* 7:1 contrast with primary */
--text-on-secondary: #000000; /* 7:1 contrast with secondary */
}
/* Focus indicators */
*:focus-visible {
outline: 3px solid var(--primary-color);
outline-offset: 2px;
}
/* Skip to content link */
.skip-to-content {
position: absolute;
top: -40px;
left: 0;
background: var(--primary-color);
color: var(--text-on-primary);
padding: 8px;
text-decoration: none;
z-index: 100000;
}
.skip-to-content:focus {
top: 0;
}
/* High contrast borders */
.high-contrast {
--main-border-color: #000000;
--input-border-width: 2px;
}
/* Color blind friendly palette */
.theme-colorblind {
--success-color: #0066cc; /* Blue instead of green */
--danger-color: #cc6600; /* Orange instead of red */
--warning-color: #663399; /* Purple instead of yellow */
}
/* Readable fonts */
body {
font-size: 16px;
line-height: 1.6;
letter-spacing: 0.02em;
}
/* Sufficient click targets */
button,
a,
input[type="checkbox"],
input[type="radio"] {
min-width: 44px;
min-height: 44px;
}
```
## Publishing Your Theme
### Package Structure
```
my-theme/
├── theme.css # Main theme file
├── preview.png # Screenshot of theme
├── README.md # Documentation
├── package.json # Metadata
└── variants/ # Optional theme variants
├── dark.css
└── light.css
```
### Metadata File
```json
{
"name": "my-awesome-theme",
"version": "1.0.0",
"description": "A beautiful theme for Trilium Notes",
"author": "Your Name",
"license": "MIT",
"trilium": {
"minVersion": "0.90.0",
"type": "theme",
"variants": ["light", "dark"],
"labels": {
"appTheme": "myAwesomeTheme",
"appCss": true
}
},
"repository": {
"type": "git",
"url": "https://github.com/username/trilium-theme"
},
"keywords": ["trilium", "theme", "dark", "modern"]
}
```
### Installation Instructions
```markdown
# My Awesome Theme
A modern theme for Trilium Notes with beautiful gradients and smooth animations.
## Installation
1. Create a new CSS code note in Trilium
2. Copy the contents of `theme.css`
3. Add the following labels to the note:
- `#appTheme=myAwesomeTheme`
- `#appCss`
4. Reload Trilium (Ctrl+R)
5. Select the theme from Options → Appearance → Theme
## Features
- Beautiful gradient accents
- Smooth animations
- Dark and light variants
- High contrast support
- Mobile responsive
## Customization
You can customize the theme by modifying the CSS variables at the top of the file.
## Screenshots
![Light Mode](./screenshots/light.png)
![Dark Mode](./screenshots/dark.png)
```
## Testing Your Theme
### Browser Developer Tools
```javascript
// Test theme in console
function testTheme() {
// Check contrast ratios
const bg = getComputedStyle(document.documentElement)
.getPropertyValue('--main-background-color');
const fg = getComputedStyle(document.documentElement)
.getPropertyValue('--main-text-color');
console.log(`Background: ${bg}`);
console.log(`Foreground: ${fg}`);
// Test all theme variables
const styles = getComputedStyle(document.documentElement);
const themeVars = Array.from(document.styleSheets)
.flatMap(sheet => Array.from(sheet.cssRules))
.filter(rule => rule.selectorText === ':root')
.flatMap(rule => Array.from(rule.style))
.filter(prop => prop.startsWith('--'));
themeVars.forEach(varName => {
const value = styles.getPropertyValue(varName);
console.log(`${varName}: ${value}`);
});
}
// Apply test theme
function applyTestTheme(cssText) {
const style = document.createElement('style');
style.id = 'test-theme';
style.textContent = cssText;
document.head.appendChild(style);
}
// Remove test theme
function removeTestTheme() {
document.getElementById('test-theme')?.remove();
}
```
### Automated Testing
```javascript
// Theme validation script
class ThemeValidator {
validate(themeCSS) {
const errors = [];
const warnings = [];
// Check required variables
const requiredVars = [
'--main-background-color',
'--main-text-color',
'--main-border-color',
'--primary-color'
];
requiredVars.forEach(varName => {
if (!themeCSS.includes(varName)) {
errors.push(`Missing required variable: ${varName}`);
}
});
// Check color contrast
const colorPairs = [
['--main-background-color', '--main-text-color'],
['--primary-color', '--primary-text-color'],
['--button-background-color', '--button-text-color']
];
// Validate color values
const colorRegex = /#[0-9a-fA-F]{3,6}|rgb\(|hsl\(/;
const matches = themeCSS.match(/--[\w-]+:\s*([^;]+);/g);
matches?.forEach(match => {
const value = match.split(':')[1].trim().replace(';', '');
if (value && !colorRegex.test(value) && !value.startsWith('var(')) {
warnings.push(`Invalid color value: ${match}`);
}
});
return { errors, warnings };
}
}
```
## Best Practices
1. **Use CSS Variables**
- Define all colors as variables
- Use semantic naming
- Provide fallback values
2. **Maintain Consistency**
- Use consistent spacing
- Keep border radii uniform
- Use a limited color palette
3. **Ensure Accessibility**
- Test color contrast
- Provide focus indicators
- Support high contrast mode
4. **Performance**
- Avoid complex selectors
- Minimize animations
- Use efficient gradients
5. **Documentation**
- Comment complex styles
- Provide customization guide
- Include screenshots
## Troubleshooting
### Theme Not Loading
- Verify labels are correct
- Check for CSS syntax errors
- Ensure note type is CSS
- Reload application
### Styles Not Applying
- Check specificity conflicts
- Verify variable names
- Use browser dev tools
- Clear cache
### Performance Issues
- Reduce animation complexity
- Optimize gradients
- Minimize shadow usage
- Profile with dev tools
## Next Steps
- Share your theme with the community
- Create theme variants
- Add custom animations
- Build a theme generator tool