Skip to content

fix: resolve CSS custom properties (var()) in SVG descendants#575

Open
Sunyi-000 wants to merge 3 commits intobubkoo:masterfrom
Sunyi-000:fix/svg-css-variables-not-resolved
Open

fix: resolve CSS custom properties (var()) in SVG descendants#575
Sunyi-000 wants to merge 3 commits intobubkoo:masterfrom
Sunyi-000:fix/svg-css-variables-not-resolved

Conversation

@Sunyi-000
Copy link

Problem

Since v1.11.12, CSS custom properties (var()) defined in external stylesheets no longer appear in exported images when used inside SVG elements (e.g., Recharts with MUI theme colors).

Root cause: PR #462 introduced a performance optimization that deep-clones SVG elements using cloneNode(true). The early return added to cloneChildren for SVG nodes means cloneCSSStyle() is never called on SVG child elements. Since cloneCSSStyle uses window.getComputedStyle() to resolve var() references to their actual values, skipping it leaves CSS variables unresolved. When the SVG is serialized, those variable definitions are unavailable in the export context, so colors render as empty/transparent.

Reproducible example: https://codesandbox.io/p/sandbox/vq6ml5

Fixes #500

Solution

After deep-cloning the SVG, iterate over all native/cloned descendant pairs and call cloneCSSStyle() on each. getComputedStyle() resolves var(--name) to its actual computed value, which then gets inlined on the cloned element.

This preserves the performance benefit of the deep-clone optimization (no async per-child cloneNode calls for the SVG subtree) while correctly resolving CSS variables.

Why not PR #521?

PR #521 simply removes the early return for SVG elements, which causes SVG children to be re-appended to an already-populated deep-cloned SVG, resulting in duplicate children. This fix avoids that by only copying styles, not re-cloning the subtree.

Changes

  • src/clone-node.ts: 17 lines changed in cloneChildren

When an SVG element is deep-cloned via cloneNode(true), its child elements
don't go through the decorate() pipeline, so cloneCSSStyle() is never called
on them. This means CSS custom properties (CSS variables) defined in external
stylesheets remain as var(--name) references in the cloned SVG children, but
those variable definitions are unavailable in the exported image context.

Fix: after deep-cloning the SVG, walk all native/cloned descendant pairs and
call cloneCSSStyle() on each. getComputedStyle() resolves var() to their
actual computed values, which then get inlined on the cloned element.

This restores the behaviour from before the deep-clone optimization (bubkoo#462)
was introduced in v1.11.12 for SVG children, while keeping the performance
benefit (no async per-child cloneNode calls) for the SVG tree structure.

Fixes bubkoo#500
@biiibooo
Copy link
Contributor

biiibooo bot commented Mar 16, 2026

👋 @Sunyi-000

💖 Thanks for opening this pull request! 💖

Please follow the contributing guidelines. And we use semantic commit messages to streamline the release process.

Examples of commit messages with semantic prefixes:

  • fix: don't overwrite prevent_default if default wasn't prevented
  • feat: add graph.scale() method
  • docs: graph.getShortestPath is now available

Things that will help get your PR across the finish line:

  • Follow the TypeScript coding style.
  • Run npm run lint locally to catch formatting errors earlier.
  • Document any user-facing changes you've made.
  • Include tests when adding/changing behavior.
  • Include screenshots and animated GIFs whenever possible.

We get a lot of pull requests on this repo, so please be patient and we will get back to you as soon as we can.

@codecov
Copy link

codecov bot commented Mar 16, 2026

Codecov Report

❌ Patch coverage is 30.00000% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.50%. Comparing base (d9b2fcf) to head (a8def89).
⚠️ Report is 7 commits behind head on master.

Files with missing lines Patch % Lines
src/clone-node.ts 30.00% 3 Missing and 4 partials ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##           master     #575   +/-   ##
=======================================
  Coverage   66.50%   66.50%           
=======================================
  Files          10       10           
  Lines         612      612           
  Branches      150      150           
=======================================
  Hits          407      407           
  Misses        144      144           
  Partials       61       61           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Adds a dedicated test case that verifies CSS custom properties (var())
defined in external stylesheets are resolved in the serialized SVG output.

The test bootstraps an SVG containing a <rect> styled with var(--rect-fill)
via an external stylesheet, then checks that the exported SVG does not
contain unresolved var() references in the cloned element's inline styles.

Covers the new code path added in clone-node.ts (lines 83-101).
…ertion

Add positive assertion that the resolved fill value (rgb(0, 128, 0)) is
present in the inline style of the cloned rect. The previous assertion
only checked for absence of var(), which passed vacuously when the
cloned rect had no inline style at all (the old broken behavior).

This assertion fails without the clone-node.ts fix and passes with it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Issue with CSS variables in charts since 1.11.12

2 participants