actualbudget/actual · error

Invalid value "${trimmedValue}" for property "${property}".

Error message

Invalid value "${trimmedValue}" for property "${property}". Only simple CSS values are allowed (colors, lengths, numbers, keywords, or var(--name)). Other functions, URLs, and complex constructs are not permitted.

What it means

validatePropertyValue allowlists values for CSS custom properties in custom themes: only colors, lengths, numbers, keywords, var(--name) references, and (for --font-*) plain font lists pass. Any other construct — function calls other than var(), URLs, complex expressions — throws this error naming the offending property and value.

Source

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

  // 6. CSS keywords: common safe keywords
  const keywordPattern =
    /^(inherit|initial|unset|revert|transparent|none|auto|normal)$/i;

  // Check if value matches any allowed pattern
  if (
    hexColorPattern.test(trimmedValue) ||
    rgbRgbaPattern.test(trimmedValue) ||
    hslHslaPattern.test(trimmedValue) ||
    lengthPattern.test(trimmedValue) ||
    numberPattern.test(trimmedValue) ||
    keywordPattern.test(trimmedValue)
  ) {
    return; // Value is allowed
  }

  // If none of the allowlist patterns match, reject the value
  throw new Error(
    `Invalid value "${trimmedValue}" for property "${property}". Only simple CSS values are allowed (colors, lengths, numbers, keywords, or var(--name)). Other functions, URLs, and complex constructs are not permitted.`,
  );
}

// ─── @font-face validation ──────────────────────────────────────────────────

/** Maximum size of a single base64-encoded font (bytes of decoded data). 2 MB. */
export const MAX_FONT_FILE_SIZE = 2 * 1024 * 1024;

/** Maximum total size of all embedded font data across all @font-face blocks. 10 MB. */
export const MAX_TOTAL_FONT_SIZE = 10 * 1024 * 1024;

/** Per-font-file fetch timeout so a hung font host can't stall theme install. */
const FONT_FETCH_TIMEOUT_MS = 15_000;

/**
 * Extract @font-face blocks from CSS. Returns the blocks and the remaining CSS.
 * Only matches top-level @font-face blocks (not nested inside other rules).

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Rewrite the value as a plain simple value: a hex/rgb color, a length, a number, or a keyword (e.g. precompute calc() results).
  2. Reference another theme variable instead of a function: use var(--other-var) where dynamic indirection is needed.
  3. Remove url() references — external assets are not allowed in theme CSS.
  4. Check the variable name against supported theme properties and drop unsupported ones.

Example fix

// before
--color-bg: url(assets/background.png);
// after
--color-bg: #1a1a2e;
Defensive patterns

Strategy: validation

Validate before calling

const SAFE = /^(#[0-9a-fA-F]{3,8}|[\w.%+-]+|var\(--[\w-]+\)|rgb|rgba|hsl|hsla|\d+(\.\d+)?(px|rem|em|%)?)/;
// pre-check each declaration value against simple-value patterns before install

Type guard

function isSimpleCssValue(v: string): boolean {
  return !/[({;@]/.test(v) || /^var\(--[\w-]+\)$/.test(v.trim());
}

Try / catch

try {
  await installTheme(css);
} catch (err) {
  const m = (err as Error).message.match(/Invalid value "(.*)" for property "(.*)"/);
  if (m) {
    // show property m[2] and value m[1] in the theme validation report
  } else throw err;
}

Prevention

When it happens

Trigger: Installing a theme whose actual.css sets a :root variable to a disallowed value, e.g. `--color-bg: url(bg.png);`, `--shadow: calc(1px + 2px);`, or any value matching none of the allowlist patterns.

Common situations: Theme CSS copied from web apps that freely use calc()/clamp()/image-set() in variables; themes referencing external assets via url(); minified CSS with exotic constructs.

Related errors


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