Strongly-typed Iconify icons for .NET. Only the referenced icons are bundled - either baked into the consuming assembly (Resource mode) or written to the build output as .svg files (Disk mode). Blazor helpers included.
IconifyBundle— the core runtime (Icon,IconPack,SvgBuilder,IconRegistry).IconifyBundle.<Pack>— one NuGet per Iconify pack (e.g.IconifyBundle.Feather). Each pack ships a precompiled, strongly-typed class (e.g.Feather) with a member per icon (e.g.Feather.Activity), the pack's icon data (a build-time file, not embedded in the assembly), and the IconifyBundle source generator (as an analyzer). So a single reference to the pack suffices - it pulls theIconifyBundleruntime in transitively and runs the generator in the consuming project. These packages are produced on demand by thePackBuildertest, which downloads each pack from the Iconify data and packs it; they are not committed to source control.- The generator detects which icons are referenced (member accesses like
Feather.Activity) and materialises only those - so the pack assemblies stay tiny and the build only carries the icons in use.
The two modes are mutually exclusive, selected by the IconifyBundleMode MSBuild property.
The referenced icons are baked into the consuming assembly, so the generated API exposes string/stream
access with no files on disk (Feather.Activity.Svg, Feather.Activity.OpenStream()). Nothing to
configure - reference a pack and use it.
The referenced icons are written to the build and publish output as .svg files (under iconifybundle/<prefix>/, e.g. to serve them as static assets), and the generated API additionally exposes file paths (Feather.ActivityPath):
<PropertyGroup>
<IconifyBundleMode>Disk</IconifyBundleMode>
</PropertyGroup>Feather.ActivityPath (and Feather.PathOf("activity")) return the path under the output directory. The strongly-typed ...Path members require C# 14 (emitted as static extension properties).
Only icons referenced through the strongly-typed API are materialised. Dynamic, string-based lookups (
Feather.PathOf(name), theIconPackindexer) resolve only icons that were also referenced statically somewhere; otherwise they throw.This selection happens at compile time, not at trim or publish time. A
not materialisederror means the icon was never referenced through a member access the generator could see - not that trimming removed it. Materialised icons run their registration from a module initializer that the trimmer preserves.
Icon icon = Feather.Activity;
// full <svg> document
string svg = icon.Svg;
using Stream stream = icon.OpenStream();The lower-level runtime API (the same Icon type the generated members return):
// An Icon carries the pack prefix, the icon name, the inner SVG body, and intrinsic size.
var icon = new Icon(
"feather",
"activity",
"""<path stroke="currentColor" d="M12 2v20"/>""",
24,
24);
// full <svg> document
var svg = icon.Svg;
// UTF-8 stream of the SVG
using var stream = icon.OpenStream();@using IconifyBundle
<Iconify Value="Feather.Activity" Width="32" Height="32" class="text-primary" />The <Iconify> component renders the icon as an inline <svg> (extra attributes like class/style are splatted onto it). There is also an Icon.ToMarkup() extension returning a MarkupString.
The static IconifyJson class reads and writes the iconify JSON format- the same shape published as @iconify-json/* and consumed by tooling like Mermaid and Naiad - so IconifyBundle icons can be handed to any iconify-format consumer and arbitrary iconify JSON can be parsed back into Icons. Three flows are supported.
Serialise any set of Icons as iconify-format JSON - to a string, a stream, or a file (sync and async). The pack prefix is taken from the icons (every Icon carries its pack Prefix), so callers do not pass it separately - and mixing icons from different packs in one call is rejected. Width/height shared by every icon are hoisted to the top level and omitted per-icon (the canonical shape; pass IconifyJsonOptions { HoistCommonSize = false } to opt out).
// The strongly-typed members from any IconifyBundle.<Pack> (e.g. Feather.Box,
// AntDesign.HomeOutlined) are the icons - just pass them in. Each Icon carries its pack
// prefix, so the prefix is derived from the icons - no need to pass it.
// As a JSON string...
var json = IconifyJson.Serialize(Feather.Box, Feather.Database);
// ...or as a stream (handy for feeding into a consumer that takes iconify JSON, e.g.
// Naiad's IconPack.Load).
using var stream = IconifyJson.OpenReadStream(Feather.Box, Feather.Database);
// ...or write directly to a file (sync/async).
IconifyJson.WriteToFile("sample.json", [Feather.Box, Feather.Database]);Options:
Indented- pretty-print, default off.HoistCommonSize- default on; factors a shared width/height up to the pack root. Setfalseto keep dimensions on every icon even when they match.Info- the iconifyinfoblock: name, author, license.
Equivalent IconPack overloads serialise the materialised icons of a runtime pack, e.g. IconifyJson.Serialize(IconPack.ForPrefix("feather")).
Parse iconify JSON (from a string, stream, or file) into an IconifyPack - the prefix, the icons, and any info block:
// Parse iconify-format JSON back into an IconifyPack (prefix + icons + optional info).
const string source =
"""
{
"prefix": "sample",
"width": 24,
"height": 24,
"icons": {
"box": {
"body": "<rect/>"
}
}
}
""";
var pack = IconifyJson.Parse(source);
// "sample"
Console.WriteLine(pack.Prefix);
// 1
Console.WriteLine(pack.Icons.Count);
foreach (var icon in pack.Icons)
{
Console.WriteLine($"{icon.Name}: {icon.Body} ({icon.Width}x{icon.Height})");
}Per-icon width/height fall back to the top-level defaults (16×16 if neither is specified, matching the iconify spec).
Every IconifyBundle.<Pack> assembly embeds its full upstream pack data as a manifest resource, so the entire @iconify-json/<pack> dataset is available at runtime - not only the icons a consumer has materialised. Pass the pack class to IconifyJson.OpenPackStream / ReadPack:
// Open the full upstream pack data embedded in any IconifyBundle.<Pack> assembly. Pass the
// strongly-typed pack class (e.g. typeof(Feather)) - the result is the entire
// @iconify-json/<pack> dataset, not just the icons your project has materialised.
using var stream = IconifyJson.OpenPackStream(packClass);
// Or get the parsed pack back directly.
var pack = IconifyJson.ReadPack(packClass);
Console.WriteLine($"{pack.Prefix}: {pack.Icons.Count} icons");Use this when the whole pack is needed (for example handing the full Feather set to Naiad's IconPack.Load); for a slim, build-time-checked subset, prefer the write APIs above with the strongly-typed members directly.
dotnet build src -c Release
src\PackBuilder\bin\Release\net10.0\PackBuilder.exe # downloads packs, builds IconifyBundle.<Pack> nugets
dotnet build IntegrationTests -c Release
dotnet build sample -c Release
https://github.com/iconify/icon-sets/blob/master/collections.md
https://www.nuget.org/packages/IconifyBundle/
One NuGet per Iconify pack. The list is generated when the packs are built.
Note: some Iconify packs are not published because their license is incompatible with redistribution in a public, commercially-consumable NuGet:
Pattern designed by gira Park from The Noun Project.