Skip to content

fix: apply iconColor to custom SVG icons from data URLs#2591

Merged
davydkov merged 2 commits intolikec4:mainfrom
kaigritun:fix/icon-color-custom-icons
Feb 11, 2026
Merged

fix: apply iconColor to custom SVG icons from data URLs#2591
davydkov merged 2 commits intolikec4:mainfrom
kaigritun:fix/icon-color-custom-icons

Conversation

@kaigritun
Copy link
Copy Markdown

@kaigritun kaigritun commented Feb 10, 2026

Summary

Fixes #2568

When custom icons are provided as SVG data URLs, they were previously rendered as <img> tags which cannot inherit the CSS color property. This caused the iconColor style property to have no effect on custom icons, while it worked correctly for bundled icons.

Problem

As reported in the issue, custom icons rendered like this:

<img src="data:image/svg+xml,%3csvg ... fill='currentColor' stroke='currentColor' ...%3e%3c/svg%3e">

The currentColor values in the SVG refer to the SVG's internal context, not the parent HTML context. Since <img> tags are opaque to CSS, the --likec4-icon-color CSS variable has no effect.

Solution

This PR decodes SVG data URLs and inlines the SVG content directly into the DOM:

<span style={{ display: 'contents' }} dangerouslySetInnerHTML={{ __html: svgContent }} />

This allows the CSS color property (set via --likec4-icon-color) to affect the currentColor values in the SVG.

Key changes in packages/diagram/src/context/IconRenderer.tsx:

  1. Added decodeSvgDataUrl() helper function that:

    • Detects if a data URL is an SVG (data:image/svg+xml)
    • Handles both base64 and URL-encoded formats
    • Returns the decoded SVG string or null for non-SVG/failed decoding
  2. Updated IconRenderer component to:

    • Check if the icon is an SVG data URL
    • Inline the SVG content if decoding succeeds
    • Fall back to <img> tag for non-SVG images or failed decoding

Behavior:

  • SVG data URLs: Inlined as raw SVG, inherits CSS color ✅
  • PNG/other data URLs: Rendered as <img> (unchanged behavior)
  • HTTP/HTTPS URLs: Rendered as <img> (unchanged behavior)
  • Bundled icons: Rendered via custom renderer (unchanged behavior)

Testing

Manually verified that:

  1. Custom SVG icons with currentColor now respect iconColor
  2. Non-SVG data URLs still work correctly
  3. External URL icons still work correctly
  4. Bundled icons still work correctly

Summary by CodeRabbit

  • New Features

    • SVG icons now support CSS color inheritance (currentColor) for easier theming.
    • SVG data URLs are automatically inlined when possible for correct styling.
  • Bug Fixes

    • Non-SVG images and cases where SVG decoding fails continue to fall back to standard image rendering to preserve compatibility.

When custom icons are provided as SVG data URLs, they were previously
rendered as <img> tags which cannot inherit the CSS color property.
This caused the iconColor style property to have no effect on custom
icons, while it worked correctly for bundled icons.

This fix decodes SVG data URLs and inlines the SVG content directly
into the DOM. This allows the CSS color property to affect the
currentColor values in the SVG, enabling iconColor to work correctly.

For non-SVG data URLs (PNG, etc.) or if decoding fails, the behavior
falls back to using <img> tags as before.

Fixes likec4#2568
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Feb 10, 2026

🦋 Changeset detected

Latest commit: c05757e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 18 packages
Name Type
@likec4/diagram Patch
@likec4/playground Patch
likec4 Patch
@likec4/vscode-preview Patch
likec4-vscode Patch
@likec4/docs-astro Patch
@likec4/style-preset Patch
@likec4/styles Patch
@likec4/config Patch
@likec4/core Patch
@likec4/generators Patch
@likec4/language-server Patch
@likec4/language-services Patch
@likec4/layouts Patch
@likec4/log Patch
@likec4/mcp Patch
@likec4/tsconfig Patch
@likec4/vite-plugin Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 10, 2026

📝 Walkthrough

Walkthrough

IconRenderer now decodes SVG data URLs and inlines the SVG markup when possible to allow CSS currentColor to affect custom icons; non-SVG data URLs or failed decodes fall back to rendering an <img> with the original URL.

Changes

Cohort / File(s) Summary
Icon rendering logic
packages/diagram/src/context/IconRenderer.tsx
Added decodeSvgDataUrl() to extract/decode SVG from base64 and URL-encoded data URLs. When a data URL contains SVG, the renderer inlines the SVG via dangerouslySetInnerHTML so currentColor can apply; otherwise it falls back to an <img> with the original URL.
Release metadata
.changeset/fix-icon-color-data-urls.md
New changeset added declaring a patch bump for @likec4/diagram describing the fix for inlining SVG data URLs to enable color inheritance.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I nibble bytes and peel each URL bright,
SVG whispers now wear colors right.
currentColor dances inlined and true,
From tiny data seeds the icons grew. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: applying iconColor to custom SVG icons from data URLs, which directly addresses the issue being fixed.
Description check ✅ Passed The PR description is comprehensive, covering the problem, solution, key changes, behavior details, and manual testing verification against the provided template checklist.
Linked Issues check ✅ Passed The PR fully addresses the linked issue #2568 by enabling SVG data URLs to be inlined so their currentColor attributes inherit the CSS color property set via iconColor.
Out of Scope Changes check ✅ Passed All code changes are directly scoped to fixing the iconColor behavior for SVG data URLs, with no unrelated modifications present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

No actionable comments were generated in the recent review. 🎉


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@packages/diagram/src/context/IconRenderer.tsx`:
- Around line 72-78: The code in IconRenderer that extracts the encoded SVG
using dataUrl.split(',')[1] can truncate SVGs containing literal commas; instead
find the first comma with indexOf(',') and use substring(index+1) to extract the
payload so all subsequent commas are preserved, then pass that full payload to
decodeURIComponent as before; update the branch in the function handling
URL-encoded data URLs to use this indexOf+substring approach and guard for a
missing comma (return null or fallthrough) rather than using split.
- Around line 111-115: The code in IconRenderer (where svgContent is injected
into the icon variable via dangerouslySetInnerHTML) exposes an XSS risk by
inserting raw decoded SVG; sanitize svgContent before assigning it to
dangerouslySetInnerHTML (e.g., use DOMPurify configured for SVG: import
DOMPurify and call DOMPurify.sanitize(svgContent, { SAFE_FOR_SVG: true,
ADD_TAGS/ADD_ATTR if needed }) or equivalent) and only fall back to
dangerouslySetInnerHTML with the sanitized string; update the IconRenderer logic
that sets icon = <span ... dangerouslySetInnerHTML={{ __html: svgContent }} />
to use the sanitized result and add tests/assertions that icons from untrusted
sources are sanitized.

Comment on lines +72 to +78
} else {
// URL-encoded format: data:image/svg+xml,%3csvg...
const encodedContent = dataUrl.split(',')[1]
if (encodedContent) {
return decodeURIComponent(encodedContent)
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

split(',')[1] truncates SVG if the data URL contains unencoded commas.

If an SVG data URL contains literal commas after the initial delimiter (e.g. data:image/svg+xml,<svg fill="rgb(0,0,0)"...), split(',')[1] returns only the fragment before the second comma. Using indexOf + substring preserves the full content.

Suggested fix
-      const encodedContent = dataUrl.split(',')[1]
-      if (encodedContent) {
-        return decodeURIComponent(encodedContent)
-      }
+      const commaIndex = dataUrl.indexOf(',')
+      if (commaIndex !== -1) {
+        const encodedContent = dataUrl.substring(commaIndex + 1)
+        if (encodedContent) {
+          return decodeURIComponent(encodedContent)
+        }
+      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} else {
// URL-encoded format: data:image/svg+xml,%3csvg...
const encodedContent = dataUrl.split(',')[1]
if (encodedContent) {
return decodeURIComponent(encodedContent)
}
}
} else {
// URL-encoded format: data:image/svg+xml,%3csvg...
const commaIndex = dataUrl.indexOf(',')
if (commaIndex !== -1) {
const encodedContent = dataUrl.substring(commaIndex + 1)
if (encodedContent) {
return decodeURIComponent(encodedContent)
}
}
}
🤖 Prompt for AI Agents
In `@packages/diagram/src/context/IconRenderer.tsx` around lines 72 - 78, The code
in IconRenderer that extracts the encoded SVG using dataUrl.split(',')[1] can
truncate SVGs containing literal commas; instead find the first comma with
indexOf(',') and use substring(index+1) to extract the payload so all subsequent
commas are preserved, then pass that full payload to decodeURIComponent as
before; update the branch in the function handling URL-encoded data URLs to use
this indexOf+substring approach and guard for a missing comma (return null or
fallthrough) rather than using split.

Copy link
Copy Markdown
Member

@davydkov davydkov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @kaigritun!

Thank you very much for the fix!
Do you mind to add changeset?

@davydkov davydkov enabled auto-merge February 11, 2026 19:10
@davydkov davydkov disabled auto-merge February 11, 2026 19:11
@davydkov davydkov enabled auto-merge February 11, 2026 19:11
@davydkov davydkov merged commit 427bec2 into likec4:main Feb 11, 2026
11 of 12 checks passed
@likec4-ci likec4-ci bot mentioned this pull request Feb 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

iconColor does not apply to custom icons

2 participants