Teaching Your IDE to Show Icons: Inline SVG Previews in Dartdoc Comments

Autocomplete on an icon font is a very specific kind of comedy. You type LucideIcons. and the IDE proudly offers you 1,994 options with names like pencilSparkles, boneOff, and squirrel. Which of these is the icon you saw in the design mock? No idea. Alt-tab to lucide.dev, search, squint, alt-tab back, pick, hot-reload, wrong one, repeat. (The icon I wanted was squirrel. It is usually not squirrel.)

The names are honest, but names are the wrong medium. An icon is a picture. The tooltip should just… show it.

It turns out you can make that happen with no plugin, no extension, and no runtime cost — by smuggling the icon itself into the doc comment. This is the story of one weird trick (sorry) that I now use across five icon-related repos.

The trick

Dart doc comments are markdown, and modern IDEs — VS Code and Android Studio both — render that markdown in hover tooltips and autocomplete detail panes. Markdown can embed images. Images can be data: URIs. And an SVG is just text.

So my lucide_icons fork's code generator base64-encodes each icon's SVG and writes it straight into the /// comment:

String _svgDataUriFromContent(String svgContent) {
  final normalizedSvg = svgContent.trim();
  final base64Svg = base64.encode(utf8.encode(normalizedSvg));
  return 'data:image/svg+xml;base64,$base64Svg';
}

The generated output, one entry per icon (base64 truncated for your scrolling finger):

  /// [![](data:image/svg+xml;base64,PCEtLSBAbGljZW5zZSBsdWNpZGUtc3RhdGljIHYxLjI0LjAg…)](https://lucide.dev/icons/pencil-sparkles)
  /// Lucide icon named "pencil sparkles".
  static const IconData pencilSparkles = IconData(
    0xE70F,
    fontFamily: 'Lucide',
    fontPackage: 'lucide_icons',
  );

Note the exact markdown shape: [![](dataUri)](docsUrl) — an image inside a link. Hovering shows the icon; clicking the preview opens that icon's page on lucide.dev. Two features for the price of one set of brackets.

Now hovering LucideIcons.pencilSparkles shows you a pencil. With sparkles. In the editor, offline, instantly.

Why this costs nothing

The part I find genuinely elegant is the cost accounting:

  • Zero runtime bytes. Comments don't survive compilation. Your app binary contains exactly as much SVG as before: none.
  • Zero network. The image data is in the file. Previews work on a plane, in a bunker, during a registry outage.
  • Zero tooling. No IDE plugin to install or keep updated. Anything that renders dartdoc markdown gets previews for free.

The price is paid entirely in the generated source file, which is now 15,958 lines long. That sounds alarming until you remember nobody reads a generated icon registry — they hover it.

What it's like to maintain

Honesty section. Embedding content in comments means the comments churn when the content does, and there are two warts I've had to manage:

The license-header churn. Each embedded SVG includes upstream's <!-- @license lucide-static vX.Y.Z --> header, so every version bump changes every base64 string — around 4,000 changed lines per icon-set update, even when the actual glyphs barely moved. Stripping the header before encoding is on my list; until then, I've made peace with diffs that read like modem noise.

Formatting is part of the contract. Early on, the generator emitted everything single-line; dart format in CI then rewrote the whole file, which made every diff all-lines-changed and quietly broke a changelog script that counted added and removed icons by grepping the diff. The fix was boring and correct: the generator pipeline formats its own output (page width 80) before committing. If a machine writes code that another machine diffs, the formatter is part of the generator, not a courtesy.

The refresh loop itself is deliberately low-drama: Dependabot watches the upstream lucide-static npm package daily, and a workflow_dispatch action regenerates the Dart file, bumps the version, and opens a PR. On the 1.17.0→1.24.0 jump, that pipeline reported 33 icons added, 0 removed, and 0 of 1,958 shared codepoints shifted — which is the number I actually care about, because a shifted codepoint silently swaps icons in every consuming app. Additions are safe; removals and shifts are the breaking changes. (A guard that auto-merges the safe case is designed but not yet wired up — currently a human, also me, pushes the button.)

One trick, five repos

The real test of a trick is whether it survives being reused. This one has spread through my icon projects like a benign infection:

  • bootstrap_icons and mingcute_icons carry a byte-identical copy of the same generate_fonts.dart — the generator grew flags (--npm-package, --css-prefix, --font-family, …) precisely so it could stop being lucide-specific. Its CSS parser now tolerates each set's dialect quirks: MingCute writes :before instead of ::before, Codicons omits a semicolon, Bootstrap has icons named things like 1-circle that need camelCase surgery.
  • flutter_icon_forge, a from-scratch icon-font toolkit, reimplements the idea independently — plain ![](dataUri) without the link wrapper — and adds an icons.lock file for append-only codepoint stability.
  • icon_font_generator, my fork of an older generator, does the HTML-flavored variant: <image width='32px' src='data:image/svg+xml;base64,…'>, which also lets you pin the render size.

Markdown-image versus HTML-tag is a genuine fork in the road, by the way: the markdown form is more portable across renderers; the HTML form gives you a width. I've shipped both and haven't fully forgiven either.

Caveats, honestly stated

This post claims IDE previews, and that claim holds all the way through pub.dev distribution: my published icon packages carry the same generated comments — flutter_mingcute is live on pub.dev — and hovers work on a plain flutter pub add install, no git dependency required.

The hosted docs are a different story, and I checked so you don't have to: pub.dev's HTML sanitizer strips data: image sources. On the flutter_mingcute API docs, each icon's <img> tag survives, still wrapped in its docs link — but with its src gone, rendering as a small, dignified nothing (checked July 2026). And it's not just my generator: other icon packages on pub.dev that embed data-URI previews lose them on their doc pages the same way. So the preview lives exactly where you make decisions, the editor, and not on the doc website. I can think of worse trade-offs — but since dartdoc could plausibly allowlist data:image/svg+xml, I'm planning to raise this upstream.

Lessons learned

  1. The doc comment is a UI surface. We treat dartdoc as prose for future readers, but the IDE renders it at the point of decision. For visual assets, put the visual there.
  2. Data URIs are the dependency-free delivery mechanism. No CDN, no assets pipeline, no plugin — just text that happens to be an image.
  3. Generated files change the economics. Nobody hand-maintains 1,994 doc comments with base64 blobs. The trick is only viable because a generator owns the file — and once it does, size objections mostly evaporate.
  4. If machines diff your output, formatting belongs to the generator. The page-width incident cost me a broken changelog script before I internalized this.

If your IDE has been making you memorize icon names, try encoding a few SVGs into the docs and hovering. And if you've tested the trick in other renderers — JetBrains hovers, GitHub's markdown view, some documentation pipeline I haven't met — comments are open, and so is my curiosity.