yewstack/yew · error
invalid attribute key
Error message
invalid attribute key
What it means
AttributeWriter::set (packages/yew/src/dom_bundle/btag/attributes.rs:176) forwards the attribute name to Element::set_attribute and unwraps with .expect("invalid attribute key"). set_attribute throws InvalidCharacterError when the name is not a valid attribute name: empty, or containing space, quote, '<', '>', '/', '=', or control characters. Literal names from the html! macro are always valid, so this panic almost always comes from dynamically built attribute names (spread attributes, attribute maps) into which invalid characters leaked.
Source
Thrown at packages/yew/src/dom_bundle/btag/attributes.rs:176
None => true,
} {
Self::set(el, k, new);
}
}
// Remove missing
for (k, old_value) in old.iter() {
if !new.contains_key(k) {
Self::remove(el, k, old_value);
}
}
}
fn set(el: &Element, key: &str, value: &AttributeOrProperty) {
match value {
AttributeOrProperty::Attribute(value) => el
.set_attribute(intern(key), value)
.expect("invalid attribute key"),
AttributeOrProperty::Property(value) => {
let key = JsValue::from_str(key);
js_sys::Reflect::set(el.as_ref(), &key, value).expect("could not set property");
}
}
}
/// Applies a value during hydration.
///
/// Hydration assumes the DOM already matches the server-rendered HTML, so
/// attributes are left untouched: re-writing them would needlessly trigger
/// side effects. In debug builds we only assert that the existing attribute
/// matches the expected value. Properties are not reflected in the HTML, so
/// they are always set.
#[cfg(feature = "hydration")]
fn hydrate_set(el: &Element, key: &str, value: &AttributeOrProperty) {
match value {
AttributeOrProperty::Attribute(value) => {View on GitHub (pinned to 0e4a05472f)
Solutions
- Validate or sanitize attribute names before they enter the VTag attribute set: reject empty strings and characters ' " < > / = and control chars
- Fix the code that constructs the offending key (the panic is a symptom; find which name is invalid by logging the key before set)
- Restrict dynamic names to a known-safe allowlist (data-, aria-, or your fixed set)
Example fix
// before
let key = format!("{} label", user_part);
attrs.insert(key, "1".into());
// after
fn is_valid_attr_name(name: &str) -> bool {
!name.is_empty() && !name.chars().any(|c| matches!(c, ' ' | '"' | '\'' | '<' | '>' | '/' | '=' ) || c.is_control())
}
assert!(is_valid_attr_name(&key));
attrs.insert(key, "1".into()); Defensive patterns
Strategy: validation
Validate before calling
fn is_valid_attr_name(name: &str) -> bool {
!name.is_empty()
&& !name.chars().any(|c| matches!(c, ' ' | '"' | '\'' | '<' | '>' | '/' | '=') || c.is_control())
}
// apply before inserting dynamic attribute names
if !is_valid_attr_name(&key) {
log::warn!("dropping invalid attribute name: {key:?}");
} else {
attrs.insert(key.into(), value.into());
} Type guard
fn safe_attr_name(name: &str) -> Option<&str> { is_valid_attr_name(name).then_some(name) } Prevention
- Prefer static attribute names in html! and pass values dynamically
- Validate names at the trust boundary when attribute maps come from JSON, CMS, or user input
- Log every dynamically built key once in debug builds to catch typos early
When it happens
Trigger: Inserting an attribute whose key is built at runtime and contains invalid characters, e.g. Attributes::insert(format!("{name} x"), ...) or an empty-string key; spreading props whose keys are derived from user input without validation.
Common situations: Generic 'rest props' spread components that pass through arbitrary keys; building data-* or aria-* names by string concatenation where a segment is empty or has whitespace; deserializing attribute maps from JSON/config that includes invalid names.
Related errors
- could not remove attribute
- could not set property
- could not remove property
- can't create namespaced element for vtag
- unkeyed child in fully keyed list
AI-assisted analysis of yewstack/yew@0e4a05472f (2026-08-22).
Data as JSON: /api/errors/06d93eea915a1bd5.
Report an issue: GitHub.