{"record":{"id":"71d78a001553f513","repo":"actualbudget/actual","slug":"invalid-css-declaration-decl","errorCode":null,"errorMessage":"Invalid CSS declaration: \"${decl}\"","messagePattern":"Invalid CSS declaration: \"(.+?)\"","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/desktop-client/src/style/customThemes.ts","lineNumber":365,"sourceCode":"function validateRootContent(rootContent: string): void {\n  // Check for forbidden at-rules inside :root\n  if (/@[a-z-]+/i.test(rootContent)) {\n    throw new Error(\n      'Theme CSS contains forbidden at-rules (@import, @media, @keyframes, etc.). Only CSS variable declarations are allowed inside :root { ... }.',\n    );\n  }\n\n  // Check for nested blocks\n  if (/\\{/.test(rootContent)) {\n    throw new Error(\n      'Theme CSS contains nested blocks or additional selectors. Only CSS variable declarations are allowed inside :root { ... }.',\n    );\n  }\n\n  for (const decl of splitDeclarations(rootContent)) {\n    const colonIndex = decl.indexOf(':');\n    if (colonIndex === -1) {\n      throw new Error(`Invalid CSS declaration: \"${decl}\"`);\n    }\n\n    const property = decl.substring(0, colonIndex).trim();\n\n    // Property must start with --\n    if (!property.startsWith('--')) {\n      throw new Error(\n        `Invalid property \"${property}\". Only CSS custom properties (starting with --) are allowed.`,\n      );\n    }\n\n    // Validate property name format\n    // CSS custom property names must:\n    // - Start with --\n    // - Not be empty (not just --)\n    // - Not end with a dash\n    // - Contain only valid characters (letters, digits, underscore, dash, but not at start/end positions)\n    if (property === '--' || property === '-') {","sourceCodeStart":347,"sourceCodeEnd":383,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/desktop-client/src/style/customThemes.ts#L347-L383","documentation":"validateThemeCss parses the :root block into declarations (split on semicolons, quote/url aware) and requires each declaration to contain a colon separating property from value. This error is thrown when a semicolon-separated chunk has no ':' at all, so it cannot be a property:value declaration. It exists because arbitrary non-declaration content inside :root could be an injection vector or simply broken CSS.","triggerScenarios":"Calling validateThemeCss with a :root block containing a stray token without a colon, e.g. ':root { --color-bg; }' (missing value), leftover text like ':root { foo bar; --a: b; }', or a truncated/corrupted paste where the colon was dropped.","commonSituations":"Hand-edited custom theme CSS with a typo, a theme copied from docs where a value was accidentally deleted, or template interpolation that produced an empty value leaving '--x: ;' fragments or bare property names.","solutions":["Find the quoted declaration in the message and add the missing colon and value (e.g. --color-bg; -> --color-bg: #ffffff;)","Remove any stray text or incomplete tokens from inside the :root block","Keep only '--property: value;' declarations inside :root; move other CSS out (it is not allowed anyway)","Validate the CSS with a quick regex per line before submitting: /^--[A-Za-z0-9_-]+\\s*:\\s*[^;]+;$/"],"exampleFix":"// before\n:root {\n  --color-bg;\n}\n// after\n:root {\n  --color-bg: #ffffff;\n}","handlingStrategy":"validation","validationCode":"function hasValidDeclarations(css: string): boolean {\n  const root = css.match(/:root\\s*\\{([\\s\\S]*?)\\}/);\n  if (!root) return false;\n  return root[1]\n    .split(';')\n    .map((d) => d.trim())\n    .filter(Boolean)\n    .every((d) => d.includes(':'));\n}","typeGuard":"const isDeclaration = (decl: string): boolean => decl.includes(':');","tryCatchPattern":"try {\n  const validated = validateThemeCss(userCss);\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Invalid CSS declaration')) {\n    showError('A declaration inside :root is missing its colon and value.');\n  }\n}","preventionTips":["Write every :root entry as complete '--name: value;' pairs","Lint theme CSS with a per-line declaration regex before saving","Never build declarations by string concatenation without a colon check","Strip stray text and empty fragments from the :root block"],"tags":["css","validation","custom-themes","syntax"],"backgroundTag":"invalid-css-declaration","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}