You don't need a PCF control or a media upload to use custom icons in Power Apps. Any Image control can render an SVG directly from a data URI, which means your icons are vector-crisp at any size, live in the app definition (no external files), and can be recolored with a formula.
The basic pattern
Set an Image control's Image property to a data URI. EncodeUrl() escapes the markup so the angle brackets and quotes survive as a URL:
"data:image/svg+xml;utf8," & EncodeUrl( "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='#2563EB' stroke-width='2'><path d='M5 12h14M12 5v14'/></svg>")Use single quotes inside the SVG so you don't have to escape the double quotes of the Power Fx string. Set the SVG's viewBox and let the Image control's Width/Height handle sizing, the vector scales cleanly.
Dynamic color, the placeholder pattern
The intuitive move is to concatenate a hex variable straight into the string. It usually works, but the Image control can cache the resulting data URI and skip re-rendering when only the color changes, so your icon appears "stuck" on the old color. The robust fix is to keep one base string with a placeholder token and inject the color with Substitute():
// EncodeUrl(varHex) turns "#2563EB" into "%232563EB" so the # survives the URI.Substitute( "data:image/svg+xml;utf8," & EncodeUrl( "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='__COLOR__'><circle cx='12' cy='12' r='10'/></svg>" ), "__COLOR__", EncodeUrl(varHex))EncodeUrl(varHex)? The token __COLOR__ is alphanumeric, so it passes through EncodeUrl untouched, but the hex you substitute in still contains a #, which is a URL fragment delimiter. Encoding it to %23 keeps the data URI valid.Driving color from state
Combine the pattern with a Switch to make status icons that recolor themselves:
With( { hex: Switch(ThisItem.Status.Value, "Active", "#16A34A", "Pending", "#D97706", "Closed", "#6B7280", "#111827" )}, Substitute( "data:image/svg+xml;utf8," & EncodeUrl( "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='__C__'><circle cx='12' cy='12' r='10'/></svg>" ), "__C__", EncodeUrl(hex) ))Two gotchas that waste an afternoon
Comma-decimal locales. If your app runs in a locale that uses a comma as the decimal separator, comma-separated numbers inside the SVG (like viewBox='0 0 24 24' or path coordinates) can be misread. Separate SVG numbers with spaces (as above), and if you build coordinate strings dynamically, use semicolons where the SVG grammar allows.
Practical tips
- Keep icons to a single path where you can, smaller strings are faster to parse.
- Store reusable icons as
App.Formulasnamed formulas (e.g.nfIcons.search) so you write the markup once. stroke='currentColor'does not inherit in a data URI, there's no CSS context. Always set an explicitfill/stroke, or use the placeholder pattern.- Grab clean 24×24 single-color paths from an open icon set (Lucide, Feather) and paste the
dattribute.