yewstack/yew · error

can't create element for vtag

Error message

can't create element for vtag

What it means

On first mount of a VTag without an xmlns attribute (and not under svg/math ancestry), Yew calls document().create_element(tag) and panics with this message if the browser rejects the call. createElement throws InvalidCharacterError for any tag string that is not a valid element name: empty strings, embedded whitespace, characters like '/', '<', '>', or names starting with a digit. In practice this panic means the tag string handed to the virtual DOM is malformed.

Solutions

  1. Log or inspect the exact tag string at the call site that builds the VTag - it violates the HTML element-name rules
  2. Validate and normalize before constructing the node: trim, lowercase, then match ^[a-z][a-z0-9-]*$ (require a hyphen for custom elements)
  3. Fall back to a safe default tag such as span, or render nothing, when the name is invalid
  4. For SVG/MathML children, place them under an <svg> or <math> parent so Yew uses the namespaced create_element_ns path instead of the plain path

Example fix

// before
let tag = user_input.clone(); // e.g. "My Widget"
html! { <@{tag}>{ "hi" }</@> }

// after
fn normalize_tag(input: &str) -> Option<String> {
    let t = input.trim().to_ascii_lowercase();
    (!t.is_empty()
        && t.starts_with(|c: char| c.is_ascii_lowercase())
        && t.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-'))
    .then_some(t)
}
match normalize_tag(&user_input) {
    Some(tag) => html! { <@{tag}>{ "hi" }</@> },
    None => html! { <span class="invalid-tag">{ "unsupported tag" }</span> },
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Run before building the VTag / dynamic tag:
fn normalize_tag(input: &str) -> Option<String> {
    let t = input.trim().to_ascii_lowercase();
    (!t.is_empty()
        && t.starts_with(|c: char| c.is_ascii_lowercase())
        && t.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-'))
    .then_some(t)
}

Type guard

fn is_valid_tag_name(tag: &str) -> bool {
    let t = tag.trim();
    !t.is_empty()
        && t.starts_with(|c: char| c.is_ascii_lowercase())
        && t.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')
}

Prevention

When it happens

Trigger: Building a tag name from an unvalidated string - VTag::new(tag), dynamic tags such as <@{tag}>...</@> in the html! macro, or tag names read from config/user input - where the string is empty, contains whitespace or '/', or starts with a non-letter.

Common situations: Tag names computed at runtime from CMS data, user input, or config without validation; format!/trim chains that can produce an empty string; typos like 'my widget' (space) or 'div/' (trailing slash); forgetting that custom element names must contain a hyphen.

Related errors


AI-assisted analysis of yewstack/yew@0e4a05472f (2026-08-22). Data as JSON: /api/errors/816057ac9235f232. Report an issue: GitHub.

Appendix: source

Thrown at packages/yew/src/dom_bundle/btag/mod.rs:301

                    document()
                        .create_element_ns(namespace, tag)
                        .expect("can't create namespaced element for vtag")
                } else {
                    thread_local! {
                        static CACHED_ELEMENTS: RefCell<HashMap<String, Element>> = RefCell::new(HashMap::with_capacity(32));
                    }

                    CACHED_ELEMENTS.with(|cache| {
                        let mut cache = cache.borrow_mut();
                        let cached = cache.get(tag).map(|el| {
                            el.clone_node()
                                .expect("couldn't clone cached element")
                                .unchecked_into::<Element>()
                        });
                        cached.unwrap_or_else(|| {
                            let to_be_cached = document()
                                .create_element(tag)
                                .expect("can't create element for vtag");
                            cache.insert(
                                tag.to_string(),
                                to_be_cached
                                    .clone_node()
                                    .expect("couldn't clone node to be cached")
                                    .unchecked_into(),
                            );
                            to_be_cached
                        })
                    })
                }
            }
        }
    }
}

impl BTag {
    /// Get the key of the underlying tag

View on GitHub (pinned to 0e4a05472f)