swc-project/swc · warning · Error

"select" start tag where end tag expected

Error message

"select" start tag where end tag expected

What it means

A `<select>` start tag was seen while the parser is already in the "in select" insertion mode. Nested selects are invalid; HTML5 makes this a parse error, then pops the existing select, resets the insertion mode, and reprocesses — effectively the new select replaces the old one.

Source

Thrown at crates/swc_html_parser/src/parser/mod.rs:5844

                            self.update_end_tag_span(popped.as_ref(), token_and_info.span);
                            self.reset_insertion_mode();
                        }
                    }
                    // A start tag whose tag name is "select"
                    //
                    // Parse error.
                    //
                    // If the stack of open elements does not have a select element in select scope,
                    // ignore the token. (fragment case)
                    //
                    // Otherwise:
                    //
                    // Pop elements from the stack of open elements until a select element has been
                    // popped from the stack.
                    //
                    // Reset the insertion mode appropriately.
                    Token::StartTag { tag_name, .. } if tag_name == "select" => {
                        self.errors.push(Error::new(
                            token_and_info.span,
                            ErrorKind::StartSelectWhereEndSelectExpected,
                        ));

                        if !self.open_elements_stack.has_in_select_scope("select") {
                            // Ignore

                            return Ok(());
                        }

                        self.open_elements_stack
                            .pop_until_tag_name_popped(&["select"]);
                        self.reset_insertion_mode();
                    }
                    // A start tag whose tag name is one of: "input", "keygen", "textarea"
                    //
                    // Parse error.
                    //

View on GitHub (pinned to 5176682b65)

Solutions

  1. Replace the outer select's content instead of nesting: close the previous `</select>` before starting a new one.
  2. When injecting dynamic options, inject `<option>` elements only — never a whole `<select>` into an open one.
  3. Lint/guard generated markup: a depth counter over `<select`/`</select` catches nesting before parsing.
  4. Recovery replaces the outer select (spec behavior) — verify the resulting tree matches what you want, and log `StartSelectWhereEndSelectExpected` from the `errors` vec.

Example fix

<!-- before: nested select -->
<select><option>a</option><select><option>b</option></select></select>

<!-- after: sibling selects -->
<select><option>a</option></select>
<select><option>b</option></select>
Defensive patterns

Strategy: validation

Validate before calling

// Pre-check: detect nested <select> before parsing
fn has_nested_select(html: &str) -> bool {
    let mut depth = 0usize;
    let lower = html.to_ascii_lowercase();
    for (i, _) in lower.match_indices('<') {
        let rest = &lower[i..];
        if rest.starts_with("<select") {
            depth += 1;
            if depth > 1 {
                return true;
            }
        } else if rest.starts_with("</select") {
            depth = depth.saturating_sub(1);
        }
    }
    false
}

Type guard

use swc_html_parser::error::{Error, ErrorKind};

fn is_nested_select_error(err: &Error) -> bool {
    matches!(err.kind(), ErrorKind::StartSelectWhereEndSelectExpected)
}

Try / catch

let mut errors = Vec::new();
let doc = parse_file_as_document(&fm, config, &mut errors)?;
if errors.iter().any(|e| matches!(e.kind(), ErrorKind::StartSelectWhereEndSelectExpected)) {
    // recovery replaced the outer select with the inner one; verify that is intended
}

Prevention

When it happens

Trigger: `<select>` inside an open select, e.g. `<select><option>a<option><select><option>b</select>`; produced by injecting a full `<select>` widget into markup that already has one open.

Common situations: Client-side/templating code that injects select markup into an existing select's options; components that render a select inside an option list by mistake; copy-paste of select blocks into select templates.

Related errors


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