{"record":{"id":"8fff614345a5a1ff","repo":"emberjs/ember.js","slug":"tag-name-elements-do-not-need-end-tags-you-s","errorCode":null,"errorMessage":"<${tag.name}> elements do not need end tags. You should remove it","messagePattern":"<(.+?)> elements do not need end tags\\. You should remove it","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/@glimmer/syntax/lib/parser/tokenizer-event-handlers.ts","lineNumber":587,"sourceCode":"    let first = getFirst(parts);\n    let last = getLast(parts);\n\n    return b.concat({\n      parts,\n      loc: this.source.spanFor(first.loc).extend(this.source.spanFor(last.loc)),\n    });\n  }\n\n  validateEndTag(\n    tag: StartTag | EndTag,\n    element: ASTv1.ParentNode,\n    selfClosing: boolean\n  ): asserts element is ASTv1.ElementNode {\n    if (voidMap.has(tag.name) && !selfClosing) {\n      // EngTag is also called by StartTag for void and self-closing tags (i.e.\n      // <input> or <br />, so we need to check for that here. Otherwise, we would\n      // throw an error for those cases.\n      throw generateSyntaxError(\n        `<${tag.name}> elements do not need end tags. You should remove it`,\n        tag.loc\n      );\n    } else if (element.type !== 'ElementNode') {\n      throw generateSyntaxError(`Closing tag </${tag.name}> without an open tag`, tag.loc);\n    } else if (element.tag !== tag.name) {\n      throw generateSyntaxError(\n        `Closing tag </${tag.name}> did not match last open tag <${element.tag}> (on line ${element.loc.startPosition.line})`,\n        tag.loc\n      );\n    }\n  }\n\n  assembleAttributeValue(\n    parts: ASTv1.AttrPart[],\n    isQuoted: boolean,\n    isDynamic: boolean,\n    span: src.SourceSpan","sourceCodeStart":569,"sourceCodeEnd":605,"githubUrl":"https://github.com/emberjs/ember.js/blob/26f97246a8bf2e28edf26ac3093da2e86c04ffc5/packages/@glimmer/syntax/lib/parser/tokenizer-event-handlers.ts#L569-L605","documentation":"validateEndTag checks every closing tag. HTML void elements (area, base, br, col, embed, hr, img, input, link, meta, param, source, track, wbr) can never have content or an end tag, so writing `</br>` etc. is rejected even though the parser also calls EndTag for self-closing start tags.","triggerScenarios":"Parsing a template containing an explicit end tag for a void element, e.g. `<br></br>` or `<input></input>` (not self-closing start tag form).","commonSituations":"Old HTML habits (`<br/>` written as `<br></br>`), copy-pasted markup from other sources, icon or spacer markup like `<img ...></img>`.","solutions":["Delete the end tag and use the void element alone: `<br>`, `<img src=...>`","If self-closing style is preferred, use `<br />` instead of `<br></br>`","Lint templates to catch void end tags across the codebase"],"exampleFix":"// before\n<br></br>\n// after\n<br>","handlingStrategy":"validation","validationCode":"const VOID = new Set(['area','base','br','col','embed','hr','img','input','link','meta','param','source','track','wbr']);\nfor (const m of template.matchAll(/<\\s*(\\w+)[^>]*>\\s*<\\/\\s*\\1\\s*>/g)) {\n  if (VOID.has(m[1])) throw new Error(`</${m[1]}> is not allowed: void element`);\n}","typeGuard":null,"tryCatchPattern":"try { parse(template) } catch (e) { if (/do not need end tags/.test(e.message)) { /* strip the end tag and reparse */ } else throw e; }","preventionTips":["Never write end tags for void elements","Prefer `<br>` or `<br />` over `<br></br>`","Add an ember-template-lint rule for void end tags"],"tags":["glimmer","handlebars","html","void-elements"],"backgroundTag":"void-element-end-tag","analyzedSha":"26f97246a8bf2e28edf26ac3093da2e86c04ffc5","analyzedAt":"2026-09-01T05:01:28.182Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}