swc-project/swc · warning · Error

Start tag "{tag_name}" seen in "table"

Error message

Start tag "{tag_name}" seen in "table"

What it means

A start tag `input` with `type=hidden` was seen in the "in table" insertion mode (parser/mod.rs:4755-4790). Hidden inputs are the only inputs allowed in table content (a spec carve-out so forms can embed data in tables); it is still a parse error. The parser records `StartTagInTable("input")`, inserts the element, and immediately pops it (input is void). Non-hidden inputs take the foster-parenting path instead and produce different errors.

Source

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

                        let is_self_closing = *is_self_closing;
                        let input_type =
                            attributes.iter().find(|attribute| attribute.name == "type");
                        let is_hidden = match &input_type {
                            Some(input_type) => match &input_type.value {
                                Some(value) if value.as_ref().eq_ignore_ascii_case("hidden") => {
                                    true
                                }
                                _ => false,
                            },
                            _ => false,
                        };

                        if input_type.is_none() || !is_hidden {
                            self.process_token_in_table_insertion_mode_anything_else(
                                token_and_info,
                            )?;
                        } else {
                            self.errors.push(Error::new(
                                token_and_info.span,
                                ErrorKind::StartTagInTable(tag_name.clone()),
                            ));

                            self.insert_html_element(token_and_info)?;
                            self.open_elements_stack.pop();

                            if is_self_closing {
                                token_and_info.acknowledged = true;
                            }
                        }
                    }
                    // A start tag whose tag name is "form"
                    //
                    // Parse error.
                    //
                    // If there is a template element on the stack of open elements, or if the form
                    // element pointer is not null, ignore the token.

View on GitHub (pinned to 5176682b65)

Solutions

  1. Move the hidden input into a cell: `<td><input type="hidden" ...></td>` (or outside the table entirely)
  2. Emit framework-injected hidden fields before the `<table>` opens
  3. Keep the placement only if you deliberately rely on the spec carve-out — the tree is still correct — but expect the parse-error warning
  4. Filter `StartTagInTable` from take_errors() when ingesting third-party grid HTML you cannot change

Example fix

<!-- before -->
<table><input type="hidden" name="id" value="7"><tr><td>x</td></tr></table>

<!-- after -->
<input type="hidden" name="id" value="7">
<table><tr><td>x</td></tr></table>
Defensive patterns

Strategy: validation

Validate before calling

// Reject <input type=hidden> (or any input) placed directly under table/tbody/tr
fn input_directly_in_table(src: &str) -> bool {
    let mut stack: Vec<String> = Vec::new();
    for tag in html_tag_tokens(src) {
        match tag {
            Start(ref n) if matches!(n.as_str(), "table"|"tbody"|"thead"|"tfoot"|"tr") => stack.push(n.clone()),
            End(ref n) if matches!(n.as_str(), "table"|"tbody"|"thead"|"tfoot"|"tr") => {
                while let Some(t) = stack.pop() { if t == *n { break; } }
            }
            Start(ref n) if n == "input" => {
                if stack.last().is_some() { return true; } // no td/th between input and table tags
            }
            _ => {}
        }
    }
    false
}

Type guard

fn is_hidden_input_in_table(e: &Error) -> bool {
    matches!(e.kind, ErrorKind::StartTagInTable(ref t) if &**t == "input")
}

Try / catch

let doc = parser.parse_document()?;
if parser.take_errors().into_iter().any(is_hidden_input_in_table) {
    // tree is still correct (spec carve-out for hidden inputs) — report only
    log::warn!("<input type=hidden> directly in table markup");
}

Prevention

When it happens

Trigger: `<table><input type="hidden" name="row_id" value="7"><tr>...` — an `<input type=hidden>` placed directly inside `<table>`, `<tbody>`, or `<tr>` rather than inside a `<td>`/`<th>` cell. Any other input type (or missing type) does not hit this arm.

Common situations: Server-rendered grids that attach per-table form data (pagination tokens, CSRF fields) directly under `<table>`; ASP.NET WebForms-era patterns; auto-injected hidden fields from frameworks that assume body context.

Related errors


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