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.
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 tagView on GitHub (pinned to 0e4a05472f)
Solutions
- Log or inspect the exact tag string at the call site that builds the VTag - it violates the HTML element-name rules
- Validate and normalize before constructing the node: trim, lowercase, then match ^[a-z][a-z0-9-]*$ (require a hyphen for custom elements)
- Fall back to a safe default tag such as span, or render nothing, when the name is invalid
- 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
- Never pass unvalidated strings as tag names; validate at the boundary where user/config data enters
- Remember custom element names must contain a hyphen (my-widget, not mywidget)
- Keep dynamic tags behind a small allowlist when the set is known
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
- failed to create detached element
- invalid attribute key
- could not set property
- could not remove attribute
- could not remove property
AI-assisted analysis of yewstack/yew@0e4a05472f (2026-08-22).
Data as JSON: /api/errors/816057ac9235f232.
Report an issue: GitHub.