Icons

@ng-native/icons renders the same icon sets a web app already uses, as real native shapes instead of an innerHTML blob. An icon set from @ng-icons/heroicons, @ng-icons/lucide or any other @ng-icons/* package is a plain object of strings of SVG markup, and provideIcons is ordinary dependency injection - none of that changes here. What changes is rendering: on the web @ng-icons/core's NgIcon inserts the markup with innerHTML and sizes it with CSS custom properties; this package's NgIcon parses the same markup and commits it as react-native-svg's native views instead. React Native has no innerHTML to insert into and no CSS engine to size against, so there is no other way to get an SVG on screen.

Import
import { NgIcon } from '@ng-native/icons';
Template
<ng-icon> once NgIcon is in the component's imports

Setup

Install react-native-svg alongside whichever icon sets you want, and provide the icons the same way you would on the web:

npm install @ng-native/icons @ng-icons/core @ng-icons/heroicons
npx expo install react-native-svg
import { Component } from '@angular/core';
import { NgIcon } from '@ng-native/icons';
import { provideIcons } from '@ng-icons/core';
import { heroBookOpen, heroAcademicCap } from '@ng-icons/heroicons/outline';

@Component({
  selector: 'app-root',
  imports: [NgIcon],
  providers: [provideIcons({ heroBookOpen, heroAcademicCap })],
  template: `<ng-icon name="heroBookOpen" [size]="28" color="#ff9f0a" />`,
})
export class App {}

provideIcons can be called at any level of the injector tree, and a provideIcons closer to a component wins over one further up - the same layering ng-icons has on the web. name accepts either spelling: the camel-cased key the icon was provided under (heroBookOpen) or the kebab-case form (hero-book-open); both resolve to the same icon.

If a name is not found under any provideIcons in scope, the icon asks the loader from provideNgIconLoader, if there is one, as it does on the web. The loader can return the markup as a string, a promise or an observable, and withCaching() asks it once per name.

import { HttpClient } from '@angular/common/http';
import { inject } from '@angular/core';
import { provideNgIconLoader, withCaching } from '@ng-icons/core';

export const iconLoader = provideNgIconLoader(
  (name) =>
    inject(HttpClient).get(`https://example.com/icons/${name}.svg`, { responseType: 'text' }),
  withCaching(),
);

The HttpClient there is the one provideNativeHttpClient() from @ng-native/platform/http provides.

If neither has the icon, it renders nothing and, in a dev build, logs a console warning naming the missing icon. There is no fallback glyph, so a typo'd name fails visibly in development and silently in production - check the console the first time an icon does not show up.

Color and stroke width

An icon set built for the web expresses two things in CSS: the currentColor a stroke or fill resolves against, and a stroke width carried as a custom property. Both become inputs here instead:

  • color is set on the icon's host, and native paints every currentColor brush in the icon from it directly. Changing color recolors every stroke and fill without re-parsing or rebuilding the icon's shapes.
  • strokeWidth fills in var(--ng-icon__stroke-width, 1.5), which is how the heroicons outline set (and others built the same way) carry their line weight.
<ng-icon name="heroBookOpen" [size]="20" color="#0a84ff" [strokeWidth]="2" />

color can be a token, color="var(--accent)", which resolves against the tokens in scope for the icon.

provideNgIconsConfig({ size, color, strokeWidth }) from @ng-icons/core sets the default of each for every icon under it. An icon's own input wins over the config.

Raw markup

Pass svg instead of name to render markup that did not come from provideIcons - fetched at runtime, or generated by your own code:

<ng-icon [svg]="mySvgString" [size]="24" />

The parser behind this is intentionally narrow: it recognizes svg, g, path, circle, ellipse, rect, line, polyline, polygon, defs, mask, clipPath, use, linearGradient, radialGradient, stop, text and tspan. A mask or clip-path attribute written as url(#id) and a <use href="#id"> (or xlink:href) point at the element with that id. Anything outside it - a <style> block, a <filter>, a <pattern> - is left out of the drawing, with a warning in a development build that names the tag.

Markup fetched at runtime is read and drawn in time that grows with its length, even when it is broken: a tag, a comment or a transform that never closes is given up once. Elements nested more than 32 deep are left out, with everything inside them, and a comment left open hides the rest of the markup, as it does in a browser. An icon nests two or three deep.

A gradient in <defs> fills or strokes any shape that references it with url(#id):

protected readonly badge =
  '<svg viewBox="0 0 24 24"><defs><linearGradient id="g" x1="0" y1="0" x2="0" y2="1">' +
  '<stop offset="0" stop-color="#437dfc"/><stop offset="1" stop-color="#4ad0ef"/>' +
  '</linearGradient></defs><circle cx="12" cy="12" r="10" fill="url(#g)"/></svg>';

A stop takes a colour written out, with stop-opacity multiplied into it. A stop written as currentColor takes the icon's color input, and is black without one: a color from a stylesheet reaches a currentColor fill or stroke, and not a stop. A stop whose color is a var() is left out.

<text> draws a line of text, positioned and sized in the drawing's own units, which with a gradient fill is a wordmark:

<ng-icon [svg]="wordmark" style="width: 96px; height: 24px" />
protected readonly wordmark =
  '<svg viewBox="0 0 96 24"><defs><linearGradient id="g" x1="0" y1="0" x2="0" y2="1">' +
  '<stop offset="0" stop-color="#437dfc"/><stop offset="1" stop-color="#4ad0ef"/>' +
  '</linearGradient></defs>' +
  '<text x="0" y="18" font-size="17" font-weight="600" fill="url(#g)">Week</text></svg>';

It is a drawing, not a <text> element of the app: it does not wrap, does not follow the device's text size, and a screen reader reads the icon's accessibilityLabel rather than its characters. Use it for a logo or a header title, and <text> from @ng-native/components for everything else.

A drawing with more than one colour takes each from a custom property: a fill or a stroke in the markup whose value is a var(), as an attribute or in style, reads the token in scope for the icon and follows it, with a fallback if it needs one.

protected readonly logo =
  '<svg viewBox="0 0 10 10"><path d="M0 0L5 5" stroke="var(--brand)"/></svg>';

As an image

A place that takes an image and not a view, such as a tab bar item, takes an icon through SvgImages. render(svg, size, at) draws the markup as an ng-icon in the view container at, asks react-native-svg for it as a PNG, and takes the view away again. It answers with an image source, a PNG data URI with the screen's scale, or null when react-native-svg is not in the build or does not answer. Android draws nothing off screen, so at has to be on screen. The router uses it for an SVG tab icon: see Tabs.

Accessibility

An icon with no accessibilityLabel is treated as decorative: it is left out of the accessibility tree entirely, which is correct for an icon sitting next to a text label that already says what it means. Set accessibilityLabel when the icon is the only thing conveying meaning - an icon-only button, for example - and it becomes an accessibility element with accessibilityRole="image":

<ng-icon name="heroTrash" accessibilityLabel="Delete" />

A static aria-label="Delete" does the same.

Sizing

Icons are square and drawn from their viewBox, so size sets both width and height in points. It takes a number, a number as a string (size="32"), or the units a web app writes, 18px or 1.5rem.

With no size, an icon is var(--ng-icon__size, 1em), as on the web. Inside ng-icons' <ng-icon-stack size="32px">, or anything else that sets --ng-icon__size, it takes that size. Otherwise it is as big as the text around it. A font-size on the icon or above it sizes it, and so does a width and height. With no font size in scope that is 16 points.

API

ng-iconcomponent@ng-native/icons
import { NgIcon } from '@ng-native/icons';and add NgIcon to the component's imports

Inputs

name
string

The key the icon was provided under: heroBookOpen , or hero-book-open .

svg
string

Markup, for an icon that comes from somewhere other than a provider.

size
unknown = pointsOf(this.config.size)

Points, square. Icons are drawn from a viewBox , so this is the size on screen. A static size="32" is read as the number it spells, and size="2em" against the font size in scope, as on the web. With none, the icon is 1em : as big as the text around it, and sized by a font-size on it or above it, or by a width and height.

color
string | undefined = this.config.color

What a currentColor stroke or fill paints as. It is the host's color style, so a var() resolves against the tokens in scope, and native paints it, so binding is cheap.

strokeWidth
number | string | undefined = this.config.strokeWidth

Fills in var(--ng-icon__stroke-width, …) , which is how an outline set carries its weight.

accessibilityLabel
string

Absent leaves the icon out of the accessibility tree, which is right for a decorative one.

Angular components as native iOS and Android views, built and shipped with Expo.

An alpha. MIT licensed. Sponsor its development.

An independent project, not affiliated with or endorsed by Google, the Angular team or Expo. Angular is a trademark of Google LLC.