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
- Replace the outer select's content instead of nesting: close the previous `</select>` before starting a new one.
- When injecting dynamic options, inject `<option>` elements only — never a whole `<select>` into an open one.
- Lint/guard generated markup: a depth counter over `<select`/`</select` catches nesting before parsing.
- 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
- Inject only <option> markup into an open select, never a whole new <select>.
- Wrap injection helpers with a nested-select depth check (see validationCode).
- Close each select before rendering the next one in list widgets.
- Remember recovery semantics: the inner select wins — assert on the resulting tree in tests.
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
- "{tag_name}" start tag with "select" open
- "{tag_name}" end tag with "select" open
- No cell to close
- End of file seen and there were open elements
- Non-space character after body
AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17).
Data as JSON: /api/errors/ebbd63d2ef243721.
Report an issue: GitHub.