yewstack/yew · error · syn::Error
string literals must not contain more than one class (hint:
Error message
string literals must not contain more than one class (hint: use `{fix}`) What it means
`classes!` builds a `yew::html::Classes` value from comma-separated arguments. When an argument is a plain string literal, the macro splits it on whitespace and rejects it if it yields more than one class (classes/mod.rs:52-64), printing a hint with the correct comma-separated form. The check applies only to literals — arbitrary expressions pass through to the runtime `Classes::push`, which does accept multi-class strings.
Source
Thrown at packages/yew-macro/src/classes/mod.rs:64
fn parse(input: ParseStream) -> syn::Result<Self> {
match input.parse()? {
Expr::Lit(ExprLit {
lit: Lit::Str(lit_str),
..
}) => {
let value = lit_str.value();
let classes = value.split_whitespace().collect::<Vec<_>>();
if classes.len() > 1 {
let fix = classes
.into_iter()
.map(|class| format!("\"{class}\""))
.collect::<Vec<_>>()
.join(", ");
let msg = format!(
"string literals must not contain more than one class (hint: use `{fix}`)"
);
Err(syn::Error::new(lit_str.span(), msg))
} else {
Ok(Self::Lit(lit_str))
}
}
expr => Ok(Self::Expr(Box::new(expr))),
}
}
}
View on GitHub (pinned to 0e4a05472f)
Solutions
- Split the literal into comma-separated arguments exactly as the hint shows: `classes!("btn", "btn-primary")`
- Keep static and dynamic parts separate: `classes!("base", extra)` where `extra` is a runtime value
- If the whole list is genuinely one runtime string, pass it as an expression (e.g. a variable or `format!(...)`) so only the runtime path handles it
Example fix
// before
html! { <div class=classes!("btn btn-primary")>{ "Save" }</div> }
// after
html! { <div class=classes!("btn", "btn-primary")>{ "Save" }</div> } Defensive patterns
Strategy: validation
Validate before calling
// When class names arrive as one runtime string, build Classes outside the
// literal path — runtime pushes accept multi-class strings:
let classes = yew::html::Classes::from("btn btn-primary"); // splits on whitespace Prevention
- Adopt the one-class-per-literal habit: `classes!("btn", "btn-primary")`, especially when pasting class lists from HTML or Tailwind
- Keep dynamic classes as separate expression arguments so literals stay single-class
- Add a code-review grep for `classes!("` lines containing a space to catch pasted multi-class literals
When it happens
Trigger: `classes!("btn btn-primary")` or `class=classes!("a b")` inside `html!` — any single string-literal argument containing two or more whitespace-separated class names.
Common situations: Pasting `class="btn btn-primary"` from HTML/JSX templates or Tailwind class lists into `classes!`, or merging several single-class literals into one string during cleanup.
Related errors
- expected a valid closing tag for component note: found openi
- missing condition for `if` expression
- this `if` expression has a condition, but no block
- expected block or `if` after `else`
- duplicate key for a node in a `{loop_kind}`-loop this will c
AI-assisted analysis of yewstack/yew@0e4a05472f (2026-08-22).
Data as JSON: /api/errors/04dc42e99b96946b.
Report an issue: GitHub.