wavetermdev/waveterm · error

bind tags must be self closing

Error message

bind tags must be self closing

What it means

During HTML parsing in vdom.Bind, a start tag using the reserved bind tag names (<bind> or <bind-param>) was found that was not self-closing. Bind tags are placeholders substituted by the framework and must be written as <bind .../> — an opening <bind> tag breaks the parse and this error is raised immediately. Note the error is created fresh (errors.New) inside the token loop rather than a package sentinel.

Source

Thrown at pkg/vdom/vdom_html.go:335

		elemPath = elemPath[:len(elemPath)-1]
	}
}

func Bind(htmlStr string, params map[string]any) *VDomElem {
	htmlStr = processWhitespace(htmlStr)
	r := strings.NewReader(htmlStr)
	iter := htmltoken.NewTokenizer(r)
	var elemStack []*VDomElem
	elemStack = append(elemStack, &VDomElem{Tag: FragmentTag})
	var tokenErr error
outer:
	for {
		tokenType := iter.Next()
		token := iter.Token()
		switch tokenType {
		case htmltoken.StartTagToken:
			if token.Data == Html_BindTagName || token.Data == Html_BindParamTagName {
				tokenErr = errors.New("bind tags must be self closing")
				break outer
			}
			elem := tokenToElem(token, params)
			elemStack = pushElemStack(elemStack, elem)
		case htmltoken.EndTagToken:
			if token.Data == Html_BindTagName || token.Data == Html_BindParamTagName {
				tokenErr = errors.New("bind tags must be self closing")
				break outer
			}
			if len(elemStack) <= 1 {
				tokenErr = fmt.Errorf("end tag %q without start tag", token.Data)
				break outer
			}
			if curElemTag(elemStack) != token.Data {
				tokenErr = fmt.Errorf("end tag %q does not match start tag %q", token.Data, curElemTag(elemStack))
				break outer
			}
			elemStack = popElemStack(elemStack)

View on GitHub (pinned to a4447c1563)

Solutions

  1. Rewrite the bind tag as self-closing: <bind name="..." /> instead of <bind name="...">.</bind>.
  2. Remove any children inside the bind tag — bind tags are leaf placeholders; move child content outside the tag.
  3. Run the template through vdom.Bind early in development/tests to catch malformed bind syntax before runtime.

Example fix

// before
<div><bind key="header"><span>fallback</span></bind></div>
// after
<div><bind key="header" /><span>fallback</span></div>
Defensive patterns

Strategy: validation

Validate before calling

// pre-validate template text before vdom.Bind
if strings.Contains(html, "<bind") || strings.Contains(html, "<bind-param") {
    re := regexp.MustCompile(`<(bind|bind-param)\b[^>]*[^/]>`)
    if m := re.FindString(html); m != "" {
        return fmt.Errorf("bind tag not self-closing: %s", m)
    }
}

Try / catch

elem, err := vdom.Bind(html, params)
if err != nil && strings.Contains(err.Error(), "bind tags must be self closing") {
    return fmt.Errorf("template error: use <bind ... /> not <bind ...>...</bind>: %w", err)
}
if err != nil {
    return err
}

Prevention

When it happens

Trigger: Writing <bind ...> (with children or a plain closing tag) in HTML handed to vdom.Bind instead of the self-closing form <bind .../>; same for <bind-param> start tags.

Common situations: Porting templates written in JSX/Vue style where component tags wrap children; hand-editing HTML and forgetting the trailing slash; copy-pasting examples from non-VDOM templates.

Related errors


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/c2a68de328f190ee. Report an issue: GitHub.