CherryHQ/cherry-studio · error · Error
[theme-contract] ${sourceName} declares invalid custom prope
Error message
[theme-contract] ${sourceName} declares invalid custom property ${name} What it means
Thrown by the theme contract validator (validate-theme-contract.ts) when a CSS source file declares a custom property whose name does not match the pattern ^--[a-z0-9-]+$. This means the property name contains uppercase letters, underscores, special characters, or doesn't start with --. The validator enforces a strict kebab-case naming convention for all CSS custom properties in the theme system.
Source
Thrown at packages/ui/scripts/validate-theme-contract.ts:61
}
type SourceEntry = readonly [source: string, css: string]
function stripComments(source: string): string {
return source.replace(/\/\*[\s\S]*?\*\//g, '')
}
function escapeRegExp(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}
function extractDeclarations(source: string, sourceName: string): Declaration[] {
const declarations = [...stripComments(source).matchAll(/(?=(?:^|[;{])\s*(--[^\s:;{}]+)\s*:\s*([^;{}]+);)/g)]
return declarations.map((match) => {
const name = match[1]
if (!CUSTOM_PROPERTY_NAME_PATTERN.test(name)) {
throw new Error(`[theme-contract] ${sourceName} declares invalid custom property ${name}`)
}
return {
name,
value: match[2].trim(),
source: sourceName
}
})
}
function extractModeDeclarations(source: string, sourceName: string, selector: ':root' | '.dark'): Declaration[] {
const declarations: Declaration[] = []
const blockPattern = new RegExp(`${escapeRegExp(selector)}\\s*\\{([\\s\\S]*?)\\}`, 'g')
for (const match of stripComments(source).matchAll(blockPattern)) {
declarations.push(...extractDeclarations(match[1], sourceName))
}
View on GitHub (pinned to 726446b54c)
Solutions
- Rename the custom property to use kebab-case matching ^--[a-z0-9-]+$: only lowercase letters, digits, and hyphens after the leading --.
- If the property came from an external source, adapt it to the contract naming convention when bringing it into the theme system.
- Run the validator to confirm: npx tsx packages/ui/scripts/validate-theme-contract.ts.
Example fix
/* before */ --primary_Color: #3b82f6; --MyBackground: #ffffff; /* after — kebab-case only */ --primary-color: #3b82f6; --my-background: #ffffff;
Defensive patterns
Strategy: validation
Validate before calling
// Validate custom property names in CSS before committing
import { readFileSync } from 'node:fs'
const VALID_NAME = /^--[a-z0-9-]+$/
function checkPropertyNames(cssPath: string): void {
const source = readFileSync(cssPath, 'utf8').replace(/\/\*[\s\S]*?\*\//g, '')
const decls = [...source.matchAll(/(?=(?:^|[;{])\s*(--[^\s:;{}]+)\s*:)/g)]
for (const match of decls) {
if (!VALID_NAME.test(match[1])) {
throw new Error(`${cssPath}: invalid property name ${match[1]} — use kebab-case`)
}
}
} Prevention
- Always use kebab-case for CSS custom property names: --my-variable, not --myVariable or --my_variable.
- Run the theme contract validator (validate-theme-contract.ts) in CI to enforce naming.
- When bringing CSS from external sources, rename properties to match the kebab-case convention.
- Avoid uppercase letters, underscores, and special characters in custom property names.
When it happens
Trigger: Any CSS file processed by extractDeclarations contains a declaration like --myVar: red; (underscore), --My-Color: red; (uppercase), --color!: red; (special char), or ---triple: red; (extra dash). The regex extracts all custom property declarations and validates each name against CUSTOM_PROPERTY_NAME_PATTERN.
Common situations: A developer adds a CSS custom property using camelCase or snake_case instead of kebab-case. Copy-pasting CSS from an external library that uses a different naming convention. A typo introducing an invalid character. Mixing CSS naming conventions from different projects.
Related errors
- [theme-contract] ${sourceName} references invalid custom pro
- [theme-contract] renderer theme must use the shared generate
- [theme-contract] renderer stylesheet ${fileName} cannot use
- [theme-contract] unsupported @import syntax: ${importValue}
- [theme-contract] ${label} imports must be exactly: ${expecte
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/40650df08ba1f576.
Report an issue: GitHub.