{"record":{"id":"2eb3e4273459a8a9","repo":"Textualize/textual","slug":"unable-to-parse-selector-css-selector-r-for-att","errorCode":null,"errorMessage":"Unable to parse selector {css_selector!r} for {attribute}; check for syntax errors","messagePattern":"Unable to parse selector (.+?) for (.+?); check for syntax errors","errorType":"exception","errorClass":"OnDecoratorError","httpStatus":null,"severity":"error","filePath":"src/textual/_on.py","lineNumber":80,"sourceCode":"    if kwargs:\n        selectors.update(kwargs)\n\n    parsed_selectors: dict[str, tuple[SelectorSet, ...]] = {}\n    for attribute, css_selector in selectors.items():\n        if attribute == \"control\":\n            if message_type.control == Message.control:\n                raise OnDecoratorError(\n                    \"The message class must have a 'control' to match with the on decorator\"\n                )\n        elif attribute not in message_type.ALLOW_SELECTOR_MATCH:\n            raise OnDecoratorError(\n                f\"The attribute {attribute!r} can't be matched; have you added it to \"\n                + f\"{message_type.__name__}.ALLOW_SELECTOR_MATCH?\"\n            )\n        try:\n            parsed_selectors[attribute] = parse_selectors(css_selector)\n        except TokenError:\n            raise OnDecoratorError(\n                f\"Unable to parse selector {css_selector!r} for {attribute}; check for syntax errors\"\n            ) from None\n\n    def decorator(method: DecoratedType) -> DecoratedType:\n        \"\"\"Store message and selector in function attribute, return callable unaltered.\"\"\"\n\n        if not hasattr(method, \"_textual_on\"):\n            setattr(method, \"_textual_on\", [])\n        getattr(method, \"_textual_on\").append((message_type, parsed_selectors))\n\n        return method\n\n    return decorator\n","sourceCodeStart":62,"sourceCodeEnd":94,"githubUrl":"https://github.com/Textualize/textual/blob/06dbeef4bb70fb718236aa418ed658ef4667a126/src/textual/_on.py#L62-L94","documentation":"The string selectors passed to @on are parsed by Textual's CSS selector parser; if a selector contains a syntax error the parser raises TokenError, which @on converts to OnDecoratorError with the offending selector quoted. This happens at import/decoration time, before the app even runs. Typical causes are stray characters, unbalanced quotes/brackets, or using selector syntax the parser does not accept in this position.","triggerScenarios":"@on(Input.Submitted, \"#search\"\"), @on(Button.Pressed, \"save\"), @on(MyMsg, field=\"not a selector!\"), unmatched quotes or brackets, accidentally passing a non-selector value like a variable holding None, or concatenating strings incorrectly so spaces/operators are malformed.","commonSituations":"Typos in selector strings; f-string-built selectors that interpolate None or data containing special characters; copying selectors from browser CSS that use unsupported pseudo-classes; quotes lost during code generation or templating.","solutions":["Inspect the quoted selector in the message and fix the syntax: ids as \"#name\", classes as \".class\", combined \"#id.cls\"","If building selectors dynamically, validate/sanitize interpolated values and escape or strip '#'/'.'-prefixed data","Check the Textual CSS selector docs for the supported subset before using advanced pseudo-class syntax","Lint import time early (run the app once or import the module in tests) so decorator errors surface in CI"],"exampleFix":"# before\n@on(Input.Submitted, \"#search\"\")  # stray quote -> OnDecoratorError\n\n# after\n@on(Input.Submitted, \"#search\")","handlingStrategy":"validation","validationCode":"from textual.css.tokenize import parse_selectors  # or textual.css parse helper\n\ndef selector_is_valid(selector: str) -> bool:\n    try:\n        parse_selectors(selector)\n        return True\n    except Exception:\n        return False\n\n# assert selector_is_valid(\"#search\") before building @on decorators dynamically","typeGuard":"def is_plain_selector(s: str) -> TypeGuard[str]:\n    return bool(s) and all(ch.isalnum() or ch in \"_-\" for ch in s.lstrip(\"#.\"))","tryCatchPattern":"validate dynamically built selectors with parse_selectors in tests; static decorators fail at import, so a simple import-the-module test catches them","preventionTips":["Keep selectors simple: \"#id\", \".class\", \"#id.class\"","Sanitize data interpolated into selectors via f-strings","Add an import-smoke test per module using @on to catch parse errors in CI"],"tags":["textual","decorator","css-selector","parse-error"],"backgroundTag":"css-selector-parse-error","analyzedSha":"06dbeef4bb70fb718236aa418ed658ef4667a126","analyzedAt":"2026-08-27T02:36:57.214Z","schemaVersion":2},"datasetVersion":"2026-08-27T03:17:27.898Z"}