amruthpillai/reactive-resume · error · Error

Selector has too many combinators.

Error message

Selector has too many combinators.

What it means

Thrown by the semantic CSS compiler when a single complex selector chain accumulates more combinators than the V1 limit allows (SEMANTIC_CSS_LIMITS_V1.maxCombinatorsPerSelector = 16). The limit exists to bound compilation/matching cost in the restricted semantic stylesheet engine, which traverses a resume's semantic node tree. Combinators counted are descendant (' '), child ('>'), adjacent ('+'), and general-sibling ('~'). The check runs after each combinator is processed in compileComplex, so the error surfaces on the 17th combinator encountered.

Source

Thrown at packages/resume/src/stylesheet/selector.ts:254

function compileComplex(node: SelectorAst, context: CompileContext): CompiledComplexSelector {
	if (node.type !== "Selector") throw new Error("Expected a Selector AST.");

	const compounds: CompiledCompoundSelector[] = [];
	const combinators: Combinator[] = [];
	let selectors: CompiledSimpleSelector[] = [];
	for (const child of childrenOf(node)) {
		if (child.type === "Combinator") {
			const name = astName(child) as Combinator;
			if (![" ", ">", "+", "~"].includes(name) || selectors.length === 0) {
				throw new Error("Unsupported or misplaced combinator.");
			}
			validateCompound(selectors);
			compounds.push({ selectors });
			selectors = [];
			combinators.push(name);
			if (combinators.length > SEMANTIC_CSS_LIMITS_V1.maxCombinatorsPerSelector) {
				throw new Error("Selector has too many combinators.");
			}
			continue;
		}

		const selector = compileSimple(child, context);
		if (selector) selectors.push(selector);
	}

	if (selectors.length === 0 && compounds.length > 0) throw new Error("Selector cannot end with a combinator.");
	validateCompound(selectors);
	compounds.push({ selectors });
	const specificity = SpecificityCalculator.calculateForAST(node).toArray();
	return { compounds, combinators, specificity: [specificity[0], specificity[1], specificity[2]] };
}

function compileSelectorList(node: SelectorAst, context: CompileContext): readonly CompiledComplexSelector[] {
	if (node.type !== "SelectorList") throw new Error("Expected a SelectorList AST.");
	const selectors = childrenOf(node);

View on GitHub (pinned to 3a5b12e2a4)

Solutions

  1. Count the combinators in the offending selector; remove or collapse descendant chains so the total is 16 or fewer. Replace deep chains like 'resume page section group item' with a single attribute or role selector targeting the node directly (e.g. 'item[role="..."]').
  2. Split one oversized selector into multiple rules that each target shallower subtrees, letting the cascade combine them.
  3. Use :is() / :where() to group targets without adding top-level combinators — note that combinators inside :is/:where are compiled at depth+1 but do not inflate the outer combinator count.
  4. If the limit is genuinely too low for a legitimate use case, evaluate raising maxCombinatorsPerSelector in packages/resume/src/stylesheet/limits.ts, but treat this as a signal that the selector is over-specified.

Example fix

// before
const source = 'resume page section group block item field label value text';
const result = compileSelector(source);
// result.error === 'Selector has too many combinators.'

// after: collapse to an attribute/role-targeted selector with far fewer combinators
const source = 'item[label="name"] text';
const result = compileSelector(source);
Defensive patterns

Strategy: validation

Validate before calling

import { compileSelector } from '@reactive-resume/resume/stylesheet';

const MAX = 16; // SEMANTIC_CSS_LIMITS_V1.maxCombinatorsPerSelector
function countCombinators(source: string): number {
  return (source.match(/[ >+~](?![^[]*\])/g) ?? []).length;
}
// before compile:
if (countCombinators(source) > MAX) {
  // reject/repair the selector before calling compileSelector
}

Type guard

function isWithinCombinatorLimit(source: string): boolean {
  return countCombinators(source) <= 16;
}

Try / catch

import { compileSelector } from '@reactive-resume/resume/stylesheet';
const { selector, error } = compileSelector(source);
if (!selector) {
  // error === 'Selector has too many combinators.' etc.
  reportToUser(error);
}

Prevention

When it happens

Trigger: Authoring a semantic CSS selector with an extremely deep element chain, e.g. 'resume page section group item field label value' (more than 16 descendant hops) or a long sibling chain 'a + b + c + ... + r'. Also triggered by machine-generated stylesheets that emit deeply nested combinators. The counter increments once per combinator node parsed by css-tree, so even seemingly short text with many implicit descendants ('a b c d ...') counts each space as one combinator.

Common situations: AI-generated or auto-generated semantic CSS that over-specifies element paths. Manually authored rules that try to reach deep into the resume semantic tree instead of using attribute/role selectors. Copying browser-targeted CSS into the semantic stylesheet (which is far more restrictive). Confusion about the difference between combinators (which count) and compound selectors (which do not).

Related errors


AI-assisted analysis of amruthpillai/reactive-resume@3a5b12e2a4 (2026-08-12). Data as JSON: /api/errors/a91aced267797229. Report an issue: GitHub.