bevyengine/bevy · error
Cannot query for canvas element.
Error message
Cannot query for canvas element.
What it means
On wasm32, when Window.canvas is set to a CSS selector string, bevy_winit's winit_windows passes it to document.querySelector(selector) and .expect("Cannot query for canvas element."). An unparsable selector throws a DOM SyntaxError which web_sys surfaces as Err — i.e. this panic means the selector string itself is malformed, before any element lookup happens.
Source
Thrown at crates/bevy_winit/src/winit_windows.rs:279
#[expect(clippy::allow_attributes, reason = "`unused_mut` is not always linted")]
#[allow(
unused_mut,
reason = "This variable needs to be mutable if `cfg(target_arch = \"wasm32\")`"
)]
let mut winit_window_attributes = winit_window_attributes.with_title(window.title.as_str());
#[cfg(target_arch = "wasm32")]
{
use wasm_bindgen::JsCast;
use winit::platform::web::WindowAttributesExtWebSys;
if let Some(selector) = &window.canvas {
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let canvas = document
.query_selector(selector)
.expect("Cannot query for canvas element.");
if let Some(canvas) = canvas {
let canvas = canvas.dyn_into::<web_sys::HtmlCanvasElement>().ok();
winit_window_attributes = winit_window_attributes.with_canvas(canvas);
} else {
panic!("Cannot find element: {selector}.");
}
}
winit_window_attributes =
winit_window_attributes.with_prevent_default(window.prevent_default_event_handling);
winit_window_attributes = winit_window_attributes.with_append(true);
}
let winit_window = event_loop.create_window(winit_window_attributes).unwrap();
let name = window.title.clone();
prepare_accessibility_for_window(
event_loop,
&winit_window,View on GitHub (pinned to 396ca72708)
Solutions
- Write a valid CSS selector: prefix ids with '#' (e.g. "#my-canvas") and classes with '.'.
- If targeting a class, ensure it is uniquely applied or made specific enough ("canvas.game").
- Validate dynamically built selectors before assigning them to Window.canvas.
- Remember an empty Some("") string is also invalid — use None to let Bevy create its own canvas.
Example fix
// before
Window {
canvas: Some("my-canvas".into()), // invalid CSS selector -> panic
..default()
}
// after
Window {
canvas: Some("#my-canvas".into()), // id selector
..default()
} Defensive patterns
Strategy: validation
Validate before calling
#[cfg(target_arch = "wasm32")]
fn selector_is_valid(selector: &str) -> bool {
web_sys::window()
.and_then(|w| w.document())
.map(|d| d.query_selector(selector).is_ok()) // Err = SyntaxError from a bad selector
.unwrap_or(false)
} Type guard
fn as_canvas_selector(s: &str) -> Option<&str> {
let valid = !s.is_empty() && s.starts_with(['#', '.']);
valid.then_some(s)
} Try / catch
// The expect() panics before any Result escapes; validate the selector string // (and its match, see error 856) before assigning Window.canvas.
Prevention
- Prefix ids with '#' and classes with '.' — never pass a bare element id.
- Centralize the selector string in one constant shared by HTML and Rust.
- Use None rather than Some("") to request Bevy's own canvas.
When it happens
Trigger: Setting Window { canvas: Some("my-canvas".into()) } without the '#' id prefix or '.' class prefix; selectors with stray characters, unbalanced brackets, or empty strings that still pass Some("").
Common situations: Copy-pasting an element id into the canvas field as if it were a raw id rather than a CSS selector; dynamically building selector strings that end up empty or malformed; following examples for older Bevy versions where the field took an id.
Related errors
- Cannot find element: {selector}.
- window.canvas() can only be called in main thread.
- Attempted to insert invalid value of type {}.
- Attempted to push invalid value of type {}.
- Attempted to insert invalid value of type {}.
AI-assisted analysis of bevyengine/bevy@396ca72708 (2026-08-20).
Data as JSON: /api/errors/59a81705401b3567.
Report an issue: GitHub.