ScreenshotNeo

BlogHow-to

How to Generate Complex CSS for HTML-to-Image Templates

Choose a renderer that supports your CSS, prepare fonts and assets, and test the final image at its target dimensions.

By the ScreenshotNeo team4 October 202611 min read

To generate reliable images from complex HTML and CSS, choose the renderer before building the visual effects. A DOM-to-canvas library such as html2canvas reconstructs an image from DOM and style information; a browser-driven capture records the browser’s rendering. They do not support the same CSS in the same way. If your design relies on browser effects that html2canvas does not implement, use a real browser capture workflow and test the result at the final output dimensions.

This guide shows how to build a stable template, render it with html2canvas, capture it with Playwright when browser fidelity matters, and diagnose missing styles or assets. The examples use JavaScript and include cURL, Python, and Node.js calls to ScreenshotNeo for remote website capture.

1. Choose a rendering model

Start by deciding where rendering happens and which CSS properties the output must preserve.

Approach How it renders Use it when Tradeoffs
DOM-to-canvas, such as html2canvas Reads the DOM and styles, then builds an image using the properties it implements. You need client-side rendering and your design fits the library’s supported CSS. It is not a native screenshot; unsupported or partially implemented properties can differ from the browser display.
Browser-driven capture, such as Playwright Uses a browser engine to render the page and capture the resulting page or element. Your design depends on browser CSS behavior, or you need server-side capture. You must provision and operate a browser environment, and control browser version, fonts, assets, and page state for repeatability.

html2canvas says its screenshot is built from information available in the DOM rather than taken from the browser’s already-painted pixels. Its feature list names common layout, typography, sizing, and gradient support, and lists box-shadow, filter, mix-blend-mode, and object-fit as unsupported. Check the [html2canvas feature list](https://html2canvas.hertzen.com/features/) for the exact properties you need; support can change with releases.

Use browser capture when fidelity depends on those unsupported properties or on other browser behaviors. Neither approach makes output automatically pixel-identical across operating systems, fonts, or browser versions. Validate the exact renderer and environment you plan to ship.

2. Define the output before writing effects

Write down the output contract first. It determines the viewport, scaling, renderer, and tests.

  • Format: PNG for lossless output and transparency, JPEG for photographic output where lossy compression is acceptable, or WebP when your consumer supports it.
  • Dimensions: Set the intended CSS viewport and output pixel dimensions. For high-density output, decide whether you will scale the capture or render at a larger viewport.
  • Target environment: Choose client-side or server-side rendering and a specific browser engine where applicable.
  • Content shape: Decide whether the output captures a fixed card, a selected element, or the full page. Include the longest realistic text and image cases.
  • Dynamic state: Specify when data is ready, whether animation is frozen, and which color scheme, locale, timezone, and device viewport are expected.

Keep a fixed representative fixture and viewport while developing. A template that looks right with one short title can overflow with a long translated title or missing image.

3. Build CSS that survives capture

Prefer explicit layout and fallback content

Use explicit container dimensions, predictable grid or flex layouts, and deliberate overflow rules. Keep essential meaning in text and layout rather than making it depend only on a shadow, filter, or blend mode that the chosen renderer may not support. Add a plain background or border fallback when an effect is decorative.

.export-card {
  box-sizing: border-box;
  width: 1200px;
  min-height: 630px;
  padding: 48px;
  display: grid;
  grid-template-columns: 1fr 360px;
  gap: 32px;
  color: #172033;
  background: #f6f8fc;
  border: 1px solid #dce3ef;
  border-radius: 24px;
}

/* Keep the fallback visible if a renderer omits the shadow. */
.export-card__panel {
  background: #fff;
  border: 1px solid #e2e8f0;
  box-shadow: 0 16px 40px rgba(20, 35, 60, .14);
}

This is ordinary browser CSS, not a promise that every DOM-to-canvas renderer implements each property. Check the selected renderer’s support list and inspect actual output.

Separate page styles from export styles

Give the capture a dedicated root class and keep export-only adjustments together. This makes it easier to freeze layout, hide controls, set a known background, and inspect computed styles without changing the interactive page.

.capture-mode .toolbar,
.capture-mode .dismiss-button {
  display: none !important;
}

.capture-mode,
.capture-mode .export-card {
  animation: none !important;
  transition: none !important;
}

.capture-mode .export-card {
  background-color: #f6f8fc;
}

Use measured dimensions, not visual guesses

Long text, font substitution, and intrinsic image sizes change layout. Give images explicit width and height or aspect ratio, set sensible wrapping rules, and decide whether overflow should clip, shrink, or expand the capture. Check with representative content, including the longest expected strings.

4. Runnable client-side example with html2canvas

Install html2canvas in a browser-based application, render the target element, and download the PNG. This example waits for fonts and image decoding, then requests CORS-enabled image loading. The remote image server must allow the requesting origin; the option cannot bypass browser security rules.

import html2canvas from 'html2canvas';

async function downloadCard() {
  const element = document.querySelector('.export-card');
  if (!element) throw new Error('Could not find .export-card');

  if (document.fonts?.ready) await document.fonts.ready;
  await Promise.all(
    [...element.querySelectorAll('img')].map(async (img) => {
      if (!img.complete) {
        await new Promise((resolve) => {
          img.addEventListener('load', resolve, { once: true });
          img.addEventListener('error', resolve, { once: true });
        });
      }
      if (img.decode) await img.decode().catch(() => {});
    })
  );

  const canvas = await html2canvas(element, {
    backgroundColor: null,
    scale: 2,
    useCORS: true,
    logging: true
  });

  const link = document.createElement('a');
  link.download = 'card.png';
  link.href = canvas.toDataURL('image/png');
  link.click();
}

downloadCard().catch((error) => {
  console.error('Card export failed:', error);
});

scale increases output pixel dimensions relative to the element’s CSS dimensions and also increases memory use. backgroundColor: null requests a transparent canvas background; use an explicit color if your target format or consumer expects an opaque image. html2canvas options and behavior are version-sensitive, so check the [project documentation](https://html2canvas.github.io/html2canvas/documentation/) for the version you install.

5. Browser-driven capture with Playwright

For a server-side capture that depends on browser CSS behavior, use Playwright’s browser automation APIs. The example starts a browser, opens a page at a fixed viewport, waits for fonts and images, and captures a selected element.

import { chromium } from 'playwright';

const browser = await chromium.launch({ headless: true });
try {
  const page = await browser.newPage({
    viewport: { width: 1200, height: 800 },
    deviceScaleFactor: 2
  });
  await page.goto('http://localhost:3000/card-preview', {
    waitUntil: 'networkidle'
  });
  await page.evaluate(async () => {
    await document.fonts.ready;
    await Promise.all(
      [...document.images].map((img) =>
        img.decode().catch(() => undefined)
      )
    );
  });
  await page.locator('.export-card').screenshot({
    path: 'card.png',
    animations: 'disabled'
  });
} finally {
  await browser.close();
}

Install and run Playwright using its [official setup instructions](https://playwright.dev/docs/intro), including the browser binaries needed in your deployment environment. The [Page API](https://playwright.dev/docs/api/class-page) documents screenshots and PDF behavior. A PDF uses print media by default; if the intended PDF should use screen styles, emulate screen media before calling page.pdf(). Set page size, margins, and background printing according to the desired document.

networkidle can wait indefinitely on pages with polling, analytics, or persistent connections. If that happens, wait for a specific ready selector or application signal instead, with a timeout. Always close the browser in a finally block so failures do not leak browser processes.

6. Cross-origin assets, fonts, and iframes

Images and CORS

A browser will not let a canvas safely export arbitrary cross-origin pixels. For html2canvas, use useCORS: true only when the image server sends an appropriate Access-Control-Allow-Origin response header. Otherwise, serve the image from the same origin or use a suitable server-side proxy that is allowed to fetch it. A canvas tainted by a cross-origin image may fail when calling toDataURL() or toBlob().

Fonts

Wait for document.fonts.ready before capture. Confirm the font file actually loaded and that the capture environment has access to it. If it fails, the browser may substitute another font and alter line wrapping, baselines, and dimensions. Self-hosting the required font can make deployments more predictable, subject to the font’s license.

Iframes

html2canvas cannot read the contents of a cross-origin iframe because of browser origin security rules. If the content is under your control, render it in the same origin or capture it independently. A real browser screenshot can capture the visible page but does not grant script access to protected cross-origin frame contents.

7. Capture options and output choices

Match options to the renderer you selected; similarly named options across tools do not imply identical behavior.

Need Consider Check
Specific element Pass the element to html2canvas or use a Playwright locator screenshot. Ensure the element is visible and its full bounds fit memory and output limits.
Full page Use the renderer’s page-capture mode and a deliberate viewport. Lazy-loaded content may need scrolling or a wait before capture; verify the final page dimensions.
Retina/high density Increase scale or device scale factor. Pixel count grows with the square of scale; monitor memory and encoder time.
Transparency Use a transparent background and a format that preserves alpha, such as PNG or WebP. JPEG does not preserve alpha. Verify the consumer’s handling of transparency.
PDF Use browser PDF generation with explicit page size, margins, orientation, and media type. Playwright PDF uses print CSS by default; screen media must be selected when that is the desired layout.
Animation Disable animations or capture at a deterministic point. Confirm the tool’s animation control covers the effects used by the page.

8. Validate the artifact and prevent regressions

  1. Render a fixed fixture at the actual target dimensions and with the production browser or library version.
  2. Inspect the saved image itself, not only the source page. Check clipping, line breaks, antialiasing, transparency, image crops, and missing effects.
  3. Include cases with long text, empty fields, missing images, different aspect ratios, and the largest expected page.
  4. Compare new output against a saved baseline. Use a pixel diff as a signal, then review intentional changes; font and environment differences can produce noise.
  5. Run the same check in the deployment environment with the installed fonts and browser binaries.

The html2canvas project documents fixture rendering and pixel comparisons as part of its visual testing approach. Its repository also notes the value of controlling the environment to reduce rendering differences such as fonts. See the [html2canvas repository](https://github.com/niklasvh/html2canvas).

9. Performance, reliability, and cost

Performance

There is no universal speed ranking between a DOM reconstruction library and browser automation for every page; measure your template and deployment. Larger output dimensions and higher scale increase pixel count and memory. Reduce unnecessary page content, capture only the needed element when possible, and avoid repeating browser startup if your server architecture can safely reuse managed browser processes.

Reliability

Make readiness explicit: wait for data, fonts, images, and a target selector. Use bounded timeouts and report which readiness step failed. Pin renderer and browser versions in production, and keep a known fixture so dependency upgrades reveal visual changes. For dynamic pages, neutralize animation and set viewport, media preferences, locale, and timezone when they affect layout.

Cost

Self-hosted html2canvas avoids a screenshot API charge but still uses client CPU and memory. Browser-driven capture adds browser runtime and infrastructure requirements. For a hosted screenshot API, compare the plan and billing rules against your capture volume and failure handling; do not infer cost or speed from a single successful capture.

10. Troubleshooting

Symptom Likely cause Fix
“Why doesn’t CSS property X render correctly or only partially?” The selected DOM-to-canvas renderer does not implement that property, or only implements part of it. Check the renderer’s current feature list. Simplify or add a fallback, or capture with a real browser engine.
Shadow, filter, blend mode, or object fit is missing These properties are listed as unsupported by html2canvas. Use a simpler fallback for html2canvas or switch this output path to browser capture.
Remote image is missing The image did not load, or cross-origin policy prevented canvas access. Inspect the network response and CORS headers. Use useCORS only with server permission, or use same-origin hosting/a suitable proxy.
Export fails with a tainted-canvas security error A cross-origin image was drawn without export permission. Fix the image server’s CORS configuration or remove/replace that asset; browser security cannot be bypassed in client JavaScript.
Text wraps differently from the page A font was unavailable at capture time or substituted. Wait for document.fonts.ready, verify font requests, and use the same font environment in production.
Image is blank or partially captured with no clear error Canvas dimensions or area may exceed environment limits. Reduce dimensions or scale, capture sections separately, and inspect the result. html2canvas FAQ estimates around 32,767 px as a maximum dimension in several desktop browsers, with approximate maximum areas around 268 million pixels for Chrome/Chromium and 472 million for Firefox; these are environment-dependent estimates, not guarantees, and iOS Safari can be lower.
Playwright waits forever for network idle The page keeps network connections active, such as polling or analytics. Wait for an application-ready selector or signal with a timeout rather than relying on global network quiet.
PDF layout differs from screen Playwright’s PDF rendering uses print media by default. Use print styles intentionally, or emulate screen media before generating a screen-styled PDF.
Output changes after deployment Browser version, fonts, operating system, viewport, or content differs. Pin the browser and font environment, fix viewport and content fixtures, then compare the generated artifact.

The canvas estimates above come from the [html2canvas FAQ](https://html2canvas.github.io/html2canvas/faq/). Treat them as rough guidance only; actual limits vary by browser, device, and available memory.

11. Or skip the browser setup

For a remote website screenshot, ScreenshotNeo provides a single GET request that returns an image or PDF. Here is the cURL form:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo API documentation for request options. The same request in Python:

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

And in Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await (await import('node:fs/promises')).writeFile('shot.webp', bytes);

ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots, and 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000. It also supports HTML/CSS-to-image templates and custom CSS and JavaScript. See ScreenshotNeo for the product and the documentation for the API options.

Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.

12. FAQ

Does “complex CSS” mean html2canvas will render it?

No. html2canvas implements CSS properties individually. Check its feature list for the exact properties in your template.

Can I use html2canvas on a server?

It depends on browser globals such as window, document, and computed styles, so it is a client-side library. For server-side browser screenshots, the html2canvas FAQ points readers to browser automation such as Puppeteer or Playwright.

Will a browser screenshot always match every user’s screen?

No. Browser, operating system, fonts, viewport, content, and device scale can affect pixels. Fix those inputs where possible and test in the target environment.

Does a screenshot API replace rendering my own HTML template?

It depends on the API and task. A remote website capture takes a URL; a custom HTML/CSS template may require an API option designed for HTML/CSS input. Check the provider’s documented request parameters.

Sources