Craft Agent’s theme system allows you to fully customize the visual appearance of the application through JSON configuration files.
Creating a Custom Theme
Themes are defined in ~/.craft-agent/theme.json as JSON objects with color properties.
Basic Theme Structure
At minimum, a theme should define at least one color property:
{
"background": "#faf9fb",
"foreground": "#26242a",
"accent": "#8b5cf6"
}
Complete Theme Example
Here’s a complete theme with all semantic and surface colors:
{
"background": "oklch(0.98 0.003 265)",
"foreground": "oklch(0.185 0.01 270)",
"accent": "oklch(0.58 0.22 293)",
"info": "oklch(0.75 0.16 70)",
"success": "oklch(0.55 0.17 145)",
"destructive": "oklch(0.58 0.24 28)",
"paper": "oklch(0.99 0.002 265)",
"navigator": "oklch(0.97 0.004 265)",
"input": "oklch(0.96 0.005 265)",
"popover": "oklch(1.0 0 0)",
"dark": {
"background": "oklch(0.145 0.015 270)",
"foreground": "oklch(0.95 0.01 270)",
"accent": "oklch(0.65 0.22 293)",
"info": "oklch(0.78 0.14 70)",
"success": "oklch(0.60 0.17 145)",
"destructive": "oklch(0.65 0.22 28)",
"paper": "oklch(0.16 0.012 270)",
"navigator": "oklch(0.13 0.018 270)"
}
}
Dark Mode Configuration
Dark mode is configured using the optional dark object, which overrides light mode colors when the system is in dark mode.
Full Dark Mode Override
Define all colors in both light and dark mode:
{
"background": "#ffffff",
"foreground": "#000000",
"accent": "#8b5cf6",
"dark": {
"background": "#1a1a1a",
"foreground": "#e0e0e0",
"accent": "#a78bfa"
}
}
Partial Dark Mode Override
You can override only specific colors in dark mode:
{
"background": "oklch(0.98 0.003 265)",
"foreground": "oklch(0.185 0.01 270)",
"accent": "oklch(0.58 0.22 293)",
"info": "oklch(0.75 0.16 70)",
"dark": {
"background": "oklch(0.145 0.015 270)",
"foreground": "oklch(0.95 0.01 270)"
// accent and info will use light mode values
}
}
Scenic Mode Themes
Scenic mode enables full-window background images with glass panel effects.
Remote Image URL
{
"mode": "scenic",
"backgroundImage": "https://example.com/backgrounds/mountain.jpg",
"background": "rgba(250, 249, 251, 0.95)",
"paper": "rgba(255, 255, 255, 0.7)",
"navigator": "rgba(247, 246, 248, 0.8)",
"popoverSolid": "#faf9fb",
"accent": "oklch(0.58 0.22 293)"
}
Data URL (Base64)
For local images, use base64 data URLs:
{
"mode": "scenic",
"backgroundImage": "data:image/jpeg;base64,/9j/4AAQSkZJRg...",
"paper": "rgba(250, 249, 251, 0.9)",
"popoverSolid": "#faf9fb"
}
Important: When using scenic mode, always define popoverSolid as a fully opaque color to ensure dropdowns and modals render correctly.
CSS Variable Generation
Craft Agent converts theme configurations into CSS variables using the themeToCSS() function.
How It Works
packages/shared/src/config/theme.ts
import { themeToCSS, type ThemeOverrides } from '@craft-agent/shared/config'
const theme: ThemeOverrides = {
background: 'oklch(0.98 0.003 265)',
foreground: 'oklch(0.185 0.01 270)',
accent: '#8b5cf6',
dark: {
background: 'oklch(0.145 0.015 270)',
foreground: 'oklch(0.95 0.01 270)'
}
}
// Generate CSS for light mode
const lightCSS = themeToCSS(theme, false)
// Output:
// --background: oklch(0.98 0.003 265);
// --foreground: oklch(0.185 0.01 270);
// --accent: #8b5cf6;
// --accent-rgb: 97, 64, 172;
// Generate CSS for dark mode
const darkCSS = themeToCSS(theme, true)
// Output:
// --background: oklch(0.145 0.015 270);
// --foreground: oklch(0.95 0.01 270);
// --accent: #8b5cf6;
RGB Value Generation
themeToCSS() automatically generates RGB values for hex colors, which are used for shadow effects:
--foreground-rgb: RGB values of foreground color for shadow borders
--accent-rgb: Darkened RGB values (70% brightness) for shadow-tinted effects
const theme = {
foreground: '#8b5cf6',
accent: '#8b5cf6'
}
Surface Color Fallbacks
Surface colors automatically fall back to background if not explicitly defined:
// If only background is defined
const theme = {
background: 'oklch(0.98 0.003 265)'
}
// All surface colors fall back to background:
// --paper: oklch(0.98 0.003 265);
// --navigator: oklch(0.98 0.003 265);
// --input: oklch(0.98 0.003 265);
// --popover: oklch(0.98 0.003 265);
This allows for minimal theme definitions while ensuring all UI regions have valid colors.
Preset Themes
Preset themes are stored in ~/.craft-agent/themes/ and include additional metadata:
~/.craft-agent/themes/dracula.json
{
"name": "Dracula",
"description": "Dark theme with vibrant colors",
"author": "Dracula Theme",
"license": "MIT",
"supportedModes": ["dark"],
"background": "#282a36",
"foreground": "#f8f8f2",
"accent": "#bd93f9",
"info": "#f1fa8c",
"success": "#50fa7b",
"destructive": "#ff5555",
"shikiTheme": {
"dark": "dracula"
}
}
Preset Theme Properties
Display name of the theme
Short description of the theme’s appearance
Theme creator or maintainer
License identifier (e.g., “MIT”, “Apache-2.0”)
Array of 'light' and/or 'dark' indicating which modes the theme supports
Syntax highlighting theme configuration:
light: Shiki theme name for light mode
dark: Shiki theme name for dark mode
Theme Resolution
Craft Agent resolves themes using the resolveTheme() function, which merges app-level overrides:
packages/shared/src/config/theme.ts
import { resolveTheme } from '@craft-agent/shared/config'
// App-level theme override
const appTheme = {
accent: '#ff0000',
paper: 'oklch(0.99 0.002 265)'
}
// Resolve final theme
const finalTheme = resolveTheme(appTheme)
// Result: Merges appTheme with defaults
Syntax Highlighting Themes
Craft Agent uses Shiki for syntax highlighting. Themes can specify which Shiki theme to use:
{
"background": "#ffffff",
"foreground": "#000000",
"accent": "#8b5cf6",
"shikiTheme": {
"light": "github-light",
"dark": "github-dark"
}
}
If not specified, Craft Agent uses the default Shiki themes:
- Light mode:
github-light
- Dark mode:
github-dark
Best Practices
Use OKLCH
OKLCH provides perceptually uniform colors and better color mixing in CSS
Define Dark Mode
Always provide dark mode overrides for a complete experience
Test Contrast
Ensure sufficient contrast between foreground and background colors
Opaque Popovers
Use solid colors for popoverSolid in scenic mode themes
Validation
Craft Agent validates theme files to ensure they contain valid color values and required properties:
import { validateThemeContent } from '@craft-agent/shared/config'
const themeJSON = JSON.stringify({
background: 'oklch(0.98 0.003 265)',
foreground: 'oklch(0.185 0.01 270)'
})
const result = validateThemeContent(themeJSON, 'my-theme.json')
if (!result.valid) {
console.error('Validation errors:', result.errors)
}
Validation Rules
- At least one color required: Theme must define at least one semantic color
- Valid CSS colors: All color values must be valid CSS color formats
- Dark mode structure: If present,
dark object must have valid color properties
- Scenic mode requirements: If
mode is scenic, backgroundImage should be provided
Examples
Minimal Theme
This minimal theme only overrides the accent color, using defaults for everything else.
High Contrast Theme
{
"background": "#000000",
"foreground": "#ffffff",
"accent": "#00ff00",
"info": "#ffff00",
"success": "#00ff00",
"destructive": "#ff0000",
"dark": {
"background": "#000000",
"foreground": "#ffffff"
}
}
Themed Scenic Mode
{
"mode": "scenic",
"backgroundImage": "https://images.unsplash.com/photo-1506905925346-21bda4d32df4",
"background": "rgba(255, 255, 255, 0.85)",
"foreground": "#1a1a1a",
"accent": "#8b5cf6",
"paper": "rgba(255, 255, 255, 0.7)",
"navigator": "rgba(250, 249, 251, 0.9)",
"popover": "rgba(255, 255, 255, 0.95)",
"popoverSolid": "#ffffff",
"dark": {
"background": "rgba(26, 26, 26, 0.85)",
"foreground": "#e0e0e0",
"paper": "rgba(30, 30, 30, 0.8)",
"navigator": "rgba(20, 20, 20, 0.9)",
"popover": "rgba(26, 26, 26, 0.95)",
"popoverSolid": "#1a1a1a"
}
}
Next Steps
Theme Overview
Learn about the theme system architecture
Configuration API
Explore configuration management APIs