Lucide-derived SVG icons for Blazor, Avalonia, and .NET MAUI.
- Zero JS, zero required CSS
- Same visual system across all three .NET UI frameworks
- Native rendering per target: inline
<svg>in Blazor, nativePathshapes in Avalonia/MAUI - Tree-shakeable typed components per icon
Docs: shellicons.shellui.dev
| Package | Status |
|---|---|
ShellIcons.Blazor |
0.1.0-alpha on NuGet — 1,555 Lucide 0.475.0 icons |
ShellIcons.Maui |
Preview, unpublished — builds for Android, iOS, Mac Catalyst and Windows; see src/ShellIcons.Maui |
ShellIcons.Avalonia |
Planned — will reuse the MAUI path conversion |
- Source generators emit every icon from the vendored catalog (
LUCIDE_VERSION.txt) plus drop-incatalog/custom/icons - Blazor: suffixed components (
<ChevronRightIcon />),Icon.*factory,<ShellIcon Name>dispatcher — safe next to UI kits - MAUI: typed controls,
IconNamedispatcher,IconCatalogmetadata with alias lookup - 135 tests (xUnit + bUnit + headless MAUI), CI for both solutions, tag-driven release — see RELEASING.md
- Docs site at docs/ShellIcons.Docs (ShellDocs 0.1.7-alpha), with a searchable icon browser
- Static build for GitHub Pages (shellicons.shellui.dev) — deploys on push to
main - MAUI: check rendering on Android/iOS devices, then publish
- Avalonia target
Not yet published. During development:
<ProjectReference Include="path/to/src/ShellIcons.Blazor/ShellIcons.Blazor.csproj" />Four ways to render an icon — all produce identical SVG. Only the first two are safe to use next to a UI kit.
| API | Import | Collides with UI kits? | Tree-shakeable? | Use for |
|---|---|---|---|---|
<ChevronRightIcon /> suffixed component |
@using ShellIcons |
✅ No | ✅ Yes | Default for markup. Reads as an icon, full parameter binding. |
@Icon.ChevronRight() factory |
@using ShellIcons |
✅ No | ✅ Yes | Icons as values — RenderFragment parameters, nav-item lists, configs. |
<ShellIcon Name="chevron-right" /> dispatcher |
@using ShellIcons |
✅ No | ❌ Roots all 1,555 | Names only known at runtime (CMS, JSON, markdown). |
<ChevronRight /> flat component |
@using ShellIcons.Icons |
✅ Yes | Icon-only files with no UI kit imported. |
One @using ShellIcons in _Imports.razor gives you the first three.
Every icon is also emitted as {Name}Icon in the root ShellIcons namespace. The suffix keeps names clear of UI-kit components and tells readers "this is an icon" at a glance — the same alias lucide-react ships.
@using ShellIcons
<ChevronRightIcon />
<ZapIcon Size="16" StrokeWidth="1.5" />
<TriangleAlertIcon Class="text-warning" />
<XIcon Title="Close dialog" />One exception: Lucide's shell icon would be ShellIcon, which is the dispatcher, so it has no suffixed form — use @Icon.Shell().
Every icon has a static method on Icon returning a RenderFragment. Use it wherever an icon is data rather than markup:
@* A component parameter typed RenderFragment *@
<NavItem Href="/" Label="Home" Icon="@Icon.House()" />
@* A list built in C# *@
@code {
private readonly (string Label, RenderFragment Icon)[] _items =
[
("Inbox", Icon.Inbox()),
("Settings", Icon.Settings(size: "16")),
];
}A call with no arguments returns a cached fragment, so @Icon.Plus() in a hot render path doesn't allocate.
Runtime lookup by kebab-case name, for icons whose identity is data-driven. Referencing it roots the full catalog, so prefer the two forms above when the name is known at compile time.
<ShellIcon Name="chevron-right" />
<ShellIcon Name="@page.IconName" Size="20" />@using ShellIcons.Icons puts all 1,555 unprefixed names into your tag lookup. With a UI kit that has Badge, Table, Router, etc., you get RZ9985 collisions.
Workarounds that don't work (verified by real integrators):
@using Icon = ShellIcons.Iconsthen<Icon.Plus />— Razor's tag matcher ignores namespace aliases; the tag compiles as an unknown HTML element and renders blank.- Type aliases (
@using Badge = MyApp.UI.Badge) — ignored by the tag matcher too.
Use the suffixed components instead, or fully qualify: <ShellIcons.Icons.ChevronRight />.
Buttons and similar containers — just put the icon in the content. No Icon slot needed; the component's CSS sizes child SVGs (shadcn's [&_svg]:size-4):
<Button><ChevronRightIcon /> Next</Button>Components that place the icon somewhere specific (input adornments, alerts, nav items) take a RenderFragment parameter — pass the factory:
<Input StartIcon="@Icon.Search()" Placeholder="Search…" />Put handlers on a wrapping <button> — screen readers expect that. If you must attach one to the icon itself, drop the @:
<button @onclick="Save" aria-label="Save"><SaveIcon /></button> @* ✅ recommended *@
<SaveIcon onclick="@(() => Save())" /> @* ✅ works *@
<SaveIcon @onclick="Save" /> @* ❌ throws — see below *@On a component, Razor passes @onclick="Save" as a plain string named @onclick; the browser then rejects that attribute name and the render batch fails. ShellIcons throws a clear InvalidOperationException instead. For the factory, the dictionary key is "onclick" (no @):
@Icon.Bell(additionalAttributes: new Dictionary<string, object>
{
["onclick"] = EventCallback.Factory.Create(this, HandleClick),
["data-testid"] = "notifications",
})Colors inherit from CSS color (stroke="currentColor"):
<span style="color: crimson"><TriangleAlertIcon Size="20" /></span>Without Title icons are decorative (aria-hidden="true"); with it they get role="img" + <title>:
<XIcon Title="Close dialog" /><ContentPage xmlns:icons="https://shellicons.dev/maui">
<icons:ChevronRight Size="16" />
<icons:Icon Name="{Binding StatusIcon}" Color="{DynamicResource Primary}" />
</ContentPage>Not on NuGet yet — reference src/ShellIcons.Maui from source. Docs: MAUI getting started.
shell-icons/
├── CHANGELOG.md release notes
├── RELEASING.md how to cut a release
├── LUCIDE_VERSION.txt pinned upstream tag
├── NOTICE.md Lucide ISC attribution
├── ShellIcons.slnx Blazor, generators, tests, docs — no MAUI workload needed
├── ShellIcons.Maui.slnx MAUI projects — needs `dotnet workload install maui`
├── Directory.Build.props
├── catalog/
│ ├── lucide/icons/ vendored Lucide SVGs (populated by sync-lucide.ps1)
│ └── custom/ repo-owned SVGs, drop-in
│ ├── README.md drop-in contract
│ └── icons/
├── scripts/
│ └── sync-lucide.ps1 refresh vendored Lucide catalog
├── src/
│ ├── ShellIcons.Blazor/ Razor Class Library (net8.0;net9.0)
│ ├── ShellIcons.Generator/ Blazor source generator (netstandard2.0)
│ ├── ShellIcons.Maui/ MAUI library (preview)
│ └── ShellIcons.Generator.Xaml/ MAUI source generator + SVG → path conversion
├── tests/
│ ├── ShellIcons.Generator.Tests/ xUnit: SvgParser, Naming, path conversion
│ ├── ShellIcons.Blazor.Tests/ xUnit + bUnit: components, factory, dispatcher, collisions
│ └── ShellIcons.Maui.Tests/ headless MAUI: controls, XAML, whole-catalog geometry
├── docs/
│ └── ShellIcons.Docs/ ShellDocs site — dev at http://localhost:5145
└── .github/workflows/
├── ci.yml ubuntu: ShellIcons.slnx · windows: ShellIcons.Maui.slnx
├── release.yml tag → NuGet (Trusted Publishing) + GitHub Release
└── docs.yml static build → GitHub Pages
Run the docs locally:
cd docs/ShellIcons.Docs
dotnet runServes at http://localhost:5145. Pages live in docs/ShellIcons.Docs/content/docs/*.md. Live component previews via razor:preview code fences work out of the box.
/docs/icons is a searchable browser of the entire catalog with click-to-copy component names.
Build the static site the way the deploy workflow does (needs dotnet tool install -g ShellDocs.CLI --version 0.1.7-alpha):
cd docs/ShellIcons.Docs
shelldocs build --output ../../publish --spa-fallback --site-url https://shellicons.shellui.devEvery page under content/ is prerendered to plain HTML, so any static host can serve publish/. Pages added as Razor @page routes outside content/ are not prerendered — put new pages in content/ (a component can be used from markdown, as content/docs/icons.md does).
./scripts/sync-lucide.ps1 # pinned version
./scripts/sync-lucide.ps1 -Version 0.480.0 # bump + repinDrop an SVG into catalog/custom/icons/ and rebuild — the Phase 2 source generator picks it up automatically and emits a component into ShellIcons.Blazor. Same 24×24 stroke contract as Lucide. See catalog/custom/README.md for the drop-in contract.
Custom icons live alongside Lucide-derived icons in the same namespace; consumers can't tell them apart. Use them for:
- Fill variants of Lucide icons (active-state UI, e.g.
HouseFillednext toHouse) - Brand marks and product-specific glyphs
- Full overrides of a Lucide icon — custom wins on name collision