symfony/css-selector · error · SyntaxErrorException

Unexpected pseudo-element

Error message

Unexpected pseudo-element "::%s" found %s.

What it means

SyntaxErrorException thrown by Parser::parserSelectorNode() when a pseudo-element (e.g. ::before, ::after) appears anywhere other than as the final component of a compound selector chain. CSS grammar only allows a pseudo-element at the very end of a selector, so the parser rejects any selector where more components (combinators, classes, attributes) follow a pseudo-element.

Solutions

  1. Move the pseudo-element to the end of the full selector: 'div span::before' instead of 'div::before span'.
  2. If you need to style the pseudo-element of an inner element, split into separate selectors per target element.
  3. Validate user-supplied selectors (regex or a pre-parse check for '::' followed by more selector tokens) before passing to Parser::parse().
  4. Catch Symfony\Component\CssSelector\Exception\SyntaxErrorException and surface a clear message to the caller.

Example fix

// before
$cssSelector->parse('div::before span');

// after
$cssSelector->parse('div span::before');
Defensive patterns

Strategy: validation

Validate before calling

if (preg_match('/::[a-zA-Z-]+\s*[-.#\[>+~:]/', $selector) || preg_match('/:(before|after|first-line|first-letter)\s*[-.#\[>+~]/i', $selector)) {
    throw new \InvalidArgumentException('Pseudo-element must be at the end of the selector: ' . $selector);
}

Try / catch

try {
    $nodes = (new Parser())->parse($selector);
} catch (SyntaxErrorException $e) {
    // log and skip this selector
}

Prevention

When it happens

Trigger: Calling Parser::parse() (or parseSelectorList via parse) with a selector where a pseudo-element is followed by another selector part, e.g. 'div::before span', 'a::first-line.class', 'ul ::after > li'. The check at Parser/Parser.php:135-137 fires when $pseudoElement is already set and the loop continues past end-of-selector tokens (',', EOF, ')').

Common situations: Typo or misreading of CSS spec: putting pseudo-elements mid-selector such as '.menu::before .item', concatenating user-supplied selector strings, porting selectors that browsers leniently accept, or generating selectors programmatically with the pseudo-element inserted before combinators.

Related errors


AI-assisted analysis of symfony/css-selector@08e2905152 (2026-09-14). Data as JSON: /api/errors/1b082ba0763452d3. Report an issue: GitHub.

Appendix: source

Thrown at Parser/Parser.php:136

    private function parserSelectorNode(TokenStream $stream, bool $isArgument = false, bool $insideRelativeSelector = false): Node\SelectorNode
    {
        [$result, $pseudoElement] = $this->parseSimpleSelector($stream, false, $isArgument, $insideRelativeSelector);

        while (true) {
            $stream->skipWhitespace();
            $peek = $stream->getPeek();

            if (
                $peek->isFileEnd()
                || $peek->isDelimiter([','])
                || ($isArgument && $peek->isDelimiter([')']))
            ) {
                break;
            }

            if (null !== $pseudoElement) {
                throw SyntaxErrorException::pseudoElementFound($pseudoElement, 'not at the end of a selector');
            }

            if ($peek->isDelimiter(['+', '>', '~'])) {
                $combinator = $stream->getNext()->getValue();
                $stream->skipWhitespace();
            } else {
                $combinator = ' ';
            }

            [$nextSelector, $pseudoElement] = $this->parseSimpleSelector($stream, false, $isArgument, $insideRelativeSelector);
            $result = new Node\CombinedSelectorNode($result, $combinator, $nextSelector);
        }

        return new Node\SelectorNode($result, $pseudoElement);
    }

    /**
     * @return list<array{0: string, 1: Node\SelectorNode}>

View on GitHub (pinned to 08e2905152)