B E T T E R I C O N S F O R U M B R A C O
How I built a custom property editor that brings 200,000+ icons to Umbraco CMS, from problem identification to open-source release.
Discovery
- Umbraco's built-in icon picker only ships ~400 icons with no search or filtering
- Content editors had to memorize icon names or scroll through a flat grid
- No support for popular libraries like FontAwesome, Lucide, or Material Icons
- Extending Umbraco's icons required manually adding SVGs to a specific folder, rebuilding, and restarting the backoffice, a painful workflow for every new icon
Architecture
- Chose Preact for the picker UI, lightweight and fast virtual scrolling for 200K+ icons
- Umbraco 13 and below: mounted the Preact picker inside an AngularJS directive for the backoffice
- Umbraco 14+: rewrapped the same Preact core as a Lit web component for the new Lit/UUI-based backoffice
- TypeScript throughout for type safety across the Preact ↔ Angular/Lit boundary
- Lazy-loaded icon libraries to keep the initial bundle small
Challenges
- Rendering 200K icons without freezing the browser, solved with virtualized grid + debounced search
- Bridging Preact state with two different host frameworks (AngularJS and Lit), built thin adapter layers for each
- Supporting multiple icon formats (SVG paths, font classes, components) under one API
- Ensuring the picker works offline with bundled icon metadata (~2MB compressed)
Result
- Open-sourced on GitHub with full documentation
- Published to NuGet for one-line install in any Umbraco project
- Adopted by the Umbraco community, featured in community newsletters
- Zero config required, works out of the box with the backoffice
T E C H S T A C K
U M B R A C O C M S
Platform
. N E T / C #
Core development
P R E A C T
Picker UI
T Y P E S C R I P T
Type safety
S T Y L E D - C O M P O N E N T S
Picker styling
A N G U L A R
Backoffice mount (v13−)
L I T
Backoffice mount (v14+)
R S P A C K
Build tooling
N U G E T
Package distribution
W H A T I L E A R N E D
Framework interop is hard
Bridging Preact inside two different hosts (AngularJS for v13−, Lit for v14+) requires careful state management. Property binding, event delegation, and lifecycle coordination all need explicit handling per framework.
Performance at scale needs planning
Rendering 200K items taught me that virtualization, debouncing, and lazy loading aren't optimizations, they're requirements.
DX matters for adoption
One-line install via NuGet with zero config was the single biggest factor in community adoption. API simplicity beats feature count.
Open source forces quality
Knowing the code would be public pushed me to write better docs, cleaner APIs, and handle edge cases I'd normally skip.