Capture a Lit component
Export a Lit card from its open shadow root, wait for updateComplete, and choose whether the image includes the host or only the card.
Wait for this.updateComplete, select the card from this.renderRoot, then export it. SnapDOM can walk open shadow roots; document.querySelector cannot reach their internal nodes.
Install and use SnapDOM in Lit
Install the capture library in your existing Lit project:
npm install @zumer/snapdom
Load this module in your client entry, then place <export-card></export-card> in your page.
import { LitElement, html, css } from 'lit'
import { snapdom } from '@zumer/snapdom'
class ExportCard extends LitElement {
static properties = {
title: { type: String },
saving: { state: true },
error: { state: true },
}
static styles = css`
article { padding: 24px; background: #eef3ff; border-radius: 12px; }
`
constructor() {
super()
this.title = 'Monthly report'
this.saving = false
this.error = ''
}
async save() {
this.saving = true
this.error = ''
try {
await this.updateComplete
const card = this.renderRoot.querySelector('#card')
await document.fonts.ready
await Promise.all(Array.from(card.querySelectorAll('img'), img => img.decode()))
await snapdom.download(card, { format: 'png', filename: 'lit-card' })
} catch (cause) {
this.error = cause instanceof Error ? cause.message : 'Capture failed'
} finally {
this.saving = false
}
}
render() {
return html`
<label>Card title <input .value=${this.title}
@input=${event => { this.title = event.target.value }} /></label>
<article id="card"><h2>${this.title}</h2><p>Ready to share.</p></article>
<button @click=${this.save} ?disabled=${this.saving}>Download PNG</button>
<p role="status">${this.error}</p>
`
}
}
customElements.define('export-card', ExportCard)Wait for the right Lit element
updateComplete resolves after this element finishes its update. It does not promise that every nested Lit component or asynchronous resource is ready. If a known child owns part of the report, await that child’s updateComplete before capture too:
await this.updateComplete
const chart = this.renderRoot.querySelector('report-chart')
await chart.updateComplete
await snapdom.download(this.renderRoot.querySelector('#card'), { format: 'png' })For third-party charts, use their documented ready signal. Do not add a fixed timeout and assume the subtree has settled.
Choose the host or the internal card
Capturing this exports the custom element host, including its open shadow tree and host-level styles. Capturing this.renderRoot.querySelector('#card') exports only the card, leaving the form controls out of the image. The example uses the latter.
A closed shadow root is inaccessible to code outside the component. Keep capture inside the component, where the render root reference is available, or provide an explicit export method. SnapDOM cannot bypass that browser boundary. See the Shadow DOM capture recipe for the open-root case.
Assets and server-rendered Lit
Lit’s update lifecycle does not wait for fonts or image downloads; the example waits for those resources explicitly and reports failures. If a child contains a same-origin iframe, also wait for the frame to load and use the iframe capture recipe.
In a server-rendered application, invoke the export after the browser has upgraded and hydrated the relevant components. Run this module in the client entry and capture from an interaction, rather than from the server rendering pipeline.
Frequently asked questions
Why can document.querySelector not find my card?
The card is inside the component’s shadow root. Select it with this.renderRoot.querySelector from the component, or capture the host to include its open shadow tree.
Does updateComplete include child updates?
Not by default. Wait for known child components’ updateComplete promises and for any separate data or asset readiness signals before capture.
Capture it in the browser
Install SnapDOM and turn the DOM your users already see into a portable image.
Open the demoInstallation