leptos-rs/leptos · error

there to be a <html> element

Error message

there to be a <html> element

What it means

leptos_meta's <Html> component builds its view state by calling document().document_element() and unwrapping it, since the component only makes sense when rendering the root <html> element. If the document has no root element (i.e. we are not rendering into a real browser document, or the Html component was mounted somewhere other than a document root), the expect panics with this message. The library throws it because <Html> attributes (class, lang, etc.) can only be applied to the single <html> root.

Source

Thrown at meta/src/html.rs:69

}

struct HtmlViewState<At>
where
    At: Attribute,
{
    attributes: At::State,
}

impl<At> Render for HtmlView<At>
where
    At: Attribute,
{
    type State = HtmlViewState<At>;

    fn build(self) -> Self::State {
        let el = document()
            .document_element()
            .expect("there to be a <html> element");

        let attributes = self.attributes.build(&el);

        HtmlViewState { attributes }
    }

    fn rebuild(self, state: &mut Self::State) {
        self.attributes.rebuild(&mut state.attributes);
    }
}

impl<At> AddAnyAttr for HtmlView<At>
where
    At: Attribute,
{
    type Output<SomeNewAttr: Attribute> =
        HtmlView<<At as NextAttribute>::Output<SomeNewAttr>>;

View on GitHub (pinned to 32d20f6c9d)

Solutions

  1. Ensure the server-rendered template includes a real <html>...</html> root element so document_element() is populated on hydration.
  2. Only render <Html> at the very root of the app (inside the hydration mount), not inside nested views or detached fragments.
  3. Verify hydration is running in a browser (or browser-like test harness such as wasm-bindgen-test with a DOM), not in a headless JS-only environment without a document.
  4. Check custom mount logic: if you replace document contents manually, keep the <html> element attached to the document.

Example fix

// before: rendering Html inside a nested, detached view
view! { <div><Html lang="en"/></div> }

// after: Html at the document root
view! { <Html lang="en"/> <App routes/> }
Defensive patterns

Strategy: validation

Validate before calling

let has_root = web_sys::window()
    .and_then(|w| w.document())
    .and_then(|d| d.document_element())
    .is_some();
if !has_root { console_error!("<Html> requires a document with <html> root"); return; }

Type guard

fn has_document_root() -> bool {
    web_sys::window().map(|w| w.document()).flatten()
        .and_then(|d| d.document_element()).is_some()
}

Prevention

When it happens

Trigger: Mounting the <Html> component (or a component that uses it, like <MetaProvider>/routes with html attribute props) during client-side build/hydrate when document().document_element() returns None — e.g. running hydration code outside a browser environment, or the mount target replaced/removed the <html> element.

Common situations: Running WASM hydration in a non-browser context (tests without DOM, ssr-only code accidentally hydrated), mounting a Leptos app into a fragment detached from the document, or a custom root template missing <html>.

Related errors


AI-assisted analysis of leptos-rs/leptos@32d20f6c9d (2026-09-01). Data as JSON: /api/errors/da7777aef6e5b6ef. Report an issue: GitHub.