actualbudget/actual · error

Theme CSS contains nested blocks or additional selectors. On

Error message

Theme CSS contains nested blocks or additional selectors. Only CSS variable declarations are allowed inside :root { ... }.

What it means

validateRootContent rejects any '{' character inside the :root block, meaning nested blocks or additional selectors were found. Custom theme CSS must be a flat list of CSS variable declarations inside a single :root block; nested rules or sibling selectors are not supported.

Source

Thrown at packages/desktop-client/src/style/customThemes.ts:357

}

// ─── :root block validation ─────────────────────────────────────────────────

/**
 * Validate the content inside a :root { ... } block.
 * Only CSS custom properties (--*) with safe values are allowed.
 */
function validateRootContent(rootContent: string): void {
  // Check for forbidden at-rules inside :root
  if (/@[a-z-]+/i.test(rootContent)) {
    throw new Error(
      'Theme CSS contains forbidden at-rules (@import, @media, @keyframes, etc.). Only CSS variable declarations are allowed inside :root { ... }.',
    );
  }

  // Check for nested blocks
  if (/\{/.test(rootContent)) {
    throw new Error(
      'Theme CSS contains nested blocks or additional selectors. Only CSS variable declarations are allowed inside :root { ... }.',
    );
  }

  for (const decl of splitDeclarations(rootContent)) {
    const colonIndex = decl.indexOf(':');
    if (colonIndex === -1) {
      throw new Error(`Invalid CSS declaration: "${decl}"`);
    }

    const property = decl.substring(0, colonIndex).trim();

    // Property must start with --
    if (!property.startsWith('--')) {
      throw new Error(
        `Invalid property "${property}". Only CSS custom properties (starting with --) are allowed.`,
      );
    }

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Delete nested rules and additional selectors; keep only flat `--variable: value;` declarations in :root.
  2. Fix brace balance in actual.css so the :root block contains no '{'.
  3. Style component markup is not themeable via custom themes — restrict your theme to variable overrides only.

Example fix

// before
:root { --color-bg: #fff; .header { color: red; } }
// after
:root { --color-bg: #fff; }
Defensive patterns

Strategy: validation

Validate before calling

function rootIsFlat(css) {
  const root = css.match(/:root\s*{([\s\S]*?)}/)?.[1] ?? '';
  return !root.includes('{');
}
if (!rootIsFlat(css)) throw new Error('nested block inside :root');

Try / catch

try {
  await installTheme(css);
} catch (err) {
  if ((err as Error).message.includes('nested blocks or additional selectors')) {
    // ask the author to flatten :root to variable declarations only
  } else throw err;
}

Prevention

When it happens

Trigger: A theme's actual.css contains nested braces inside :root (e.g. `:root { --x: 1; .btn { color: red; } }`) or unbalanced/extra braces that leave a '{' in the extracted root content.

Common situations: Theme authors pasting full stylesheets (component rules) into :root; malformed CSS with mismatched braces so extra blocks appear inside the root region; preprocessor output with nesting.

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/b4cd215b44e98c50. Report an issue: GitHub.