swc-project/swc · error · swc_css_parser::error::Error

Unexpected characters in ID selector

Error message

Unexpected characters in ID selector

What it means

CSS tokenizes '#...' as a Hash token with is_id=true only when the remainder would form a valid identifier; otherwise (e.g. '#123') is_id=false and it is not usable as an ID selector. The parser consumed a Hash token whose is_id flag is false, meaning the text after '#' contains characters that cannot start an identifier (digits, or unescaped specials), so it rejects it as an ID selector.

Source

Thrown at crates/swc_css_parser/src/parser/selectors/mod.rs:631

                    ErrorKind::Expected("id, class, attribute or pseudo-class selector"),
                ));
            }
        }
    }
}

impl<I> Parse<IdSelector> for Parser<I>
where
    I: ParserInput,
{
    fn parse(&mut self) -> PResult<IdSelector> {
        let span = self.input.cur_span();
        let text = match bump!(self) {
            Token::Hash {
                is_id, value, raw, ..
            } => {
                if !is_id {
                    return Err(Error::new(
                        span,
                        ErrorKind::Unexpected("characters in ID selector"),
                    ));
                }

                Ident {
                    span,
                    value,
                    raw: Some(raw),
                }
            }
            _ => {
                unreachable!()
            }
        };

        Ok(IdSelector {
            span: span!(self, span.lo),

View on GitHub (pinned to 5176682b65)

Solutions

  1. Escape the first digit with its hex escape and a space: '#\31 23abc' (escape for '1' plus separating space)
  2. Prefer a class selector '.item-123' for machine-generated names
  3. When generating selectors from IDs, run them through an identifier-escaping helper (CSS.escape in JS)

Example fix

/* before */
#1st { color: red; }
/* after */
#\31 st { color: red; }  /* or rename the id to a letter-first value */
Defensive patterns

Strategy: type-guard

Validate before calling

// IDs usable as selectors must be ident-shaped after '#'
function isCssIdent(s) { return /^--?[A-Za-z_][\w-]*$/.test(s) || /^\[0-9a-fA-F]{1,6} /.test(s); }

Type guard

// JS (browser/helpers)
function idIsSelectable(id) { return /^[A-Za-z_-][\w-]*$/.test(id); }
// escape otherwise: CSS.escape(id)

Try / catch

// JS
const sel = idIsSelectable(id) ? `#${id}` : `#${CSS.escape ? CSS.escape(id) : escapeIdent(id)}`;

Prevention

When it happens

Trigger: '#123abc {}', '#1st {}', or any '#'-selector whose name starts with a digit or otherwise is not a valid identifier without escaping.

Common situations: IDs generated from database numeric keys ('#4021') rendered into CSS by templates; anchors like '#2023-report' referenced as style selectors; authors assuming HTML's looser id rules apply to CSS ID-selector syntax.

Related errors


AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17). Data as JSON: /api/errors/5e327a9dc4011a76. Report an issue: GitHub.