{"record":{"id":"544db72b51cbd8f3","repo":"actualbudget/actual","slug":"theme-css-must-contain-root-with-css-vari","errorCode":null,"errorMessage":"Theme CSS must contain :root { ... } with CSS variable definitions. No other selectors or content allowed.","messagePattern":"Theme CSS must contain :root (.+?) with CSS variable definitions\\. No other selectors or content allowed\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/desktop-client/src/style/customThemes.ts","lineNumber":443,"sourceCode":" * @font-face blocks must appear before :root.\n * Returns the validated CSS or throws an error.\n */\nexport function validateThemeCss(css: string): string {\n  // Strip multi-line comments before validation\n  const cleaned = css.replace(/\\/\\*[\\s\\S]*?\\*\\//g, '').trim();\n\n  // Extract @font-face blocks (if any) from the CSS\n  const { fontFaceBlocks, remaining } = extractFontFaceBlocks(cleaned);\n\n  // Validate @font-face blocks (reject remote URLs, enforce size limits)\n  validateFontFaceBlocks(fontFaceBlocks);\n\n  // Now validate the remaining CSS (should be exactly :root { ... })\n  const rootMatch = remaining.match(/^:root\\s*\\{/);\n  if (!rootMatch) {\n    // If there are @font-face blocks but no :root, that's an error\n    // If there's nothing at all, that's also an error\n    throw new Error(\n      'Theme CSS must contain :root { ... } with CSS variable definitions. No other selectors or content allowed.',\n    );\n  }\n\n  const rootStart = remaining.indexOf(':root');\n  const openBrace = remaining.indexOf('{', rootStart);\n\n  if (openBrace === -1) {\n    throw new Error(\n      'Theme CSS must contain :root { ... } with CSS variable definitions. No other selectors or content allowed.',\n    );\n  }\n\n  const closeBrace = remaining.indexOf('}', openBrace + 1);\n\n  if (closeBrace === -1) {\n    throw new Error(\n      'Theme CSS must contain :root { ... } with CSS variable definitions. No other selectors or content allowed.',","sourceCodeStart":425,"sourceCodeEnd":461,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/desktop-client/src/style/customThemes.ts#L425-L461","documentation":"validateThemeCss strips comments, extracts optional @font-face blocks, and then requires the remaining CSS to start exactly with ':root {'. This error is thrown when the remaining content does not begin with a :root block — including when there is no content at all, or a selector other than :root appears first. The theme system deliberately accepts nothing besides optional @font-face blocks and a single :root variable block.","triggerScenarios":"Calling validateThemeCss('') (empty string), CSS starting with another selector like 'body { ... }' or '.theme { ... }', CSS starting with an at-rule (@media, @import), or a typo like ': root {' / ':Root {' that fails the /^:root\\s*\\{/ match.","commonSituations":"Pasting an entire stylesheet instead of only variable overrides, saving an empty theme, migrating from an older theme format that allowed full CSS, or typos in the :root selector.","solutions":["Wrap all variable definitions in a ':root { ... }' block at the start of the CSS","If the CSS is empty, add a minimal ':root { --color-bg: #ffffff; }' or cancel the save","Remove or relocate other selectors/at-rules — they are not supported in theme CSS","Fix typos in the selector: it must be exactly ':root' followed by optional whitespace and '{'"],"exampleFix":"// before\nbody {\n  --color-bg: #fff;\n}\n// after\n:root {\n  --color-bg: #fff;\n}","handlingStrategy":"validation","validationCode":"function startsWithRootBlock(css: string): boolean {\n  return /^:root\\s*\\{[\\s\\S]*\\}\\s*$/.test(css.replace(/\\/\\*[\\s\\S]*?\\*\\//g, '').trim());\n}","typeGuard":"null","tryCatchPattern":"try {\n  const validated = validateThemeCss(userCss);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('must contain :root')) {\n    showError('Theme CSS must be a single :root { ... } block (optionally preceded by @font-face blocks).');\n  }\n}","preventionTips":["Always wrap theme variables in a leading :root { ... } block","Never save an empty theme — require at least one variable","Strip other selectors/at-rules before submitting theme CSS","Verify the exact ':root' spelling (no space between : and root)"],"tags":["css","validation","custom-themes","structure"],"backgroundTag":"missing-css-root-selector","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}