Frameworks

Icons in React Native

React Native has no SVG element of its own, so glyphfox icons render through react-native-svg. The glyphfox engine turns icon data into an SVG string and SvgXml draws it natively on iOS and Android.

Install

Install glyphfox and react-native-svg. In an Expo project, let Expo pick the react-native-svg version that matches your SDK; in a bare project, install the iOS pods afterwards.

terminal
npm install glyphfox react-native-svg
# Expo
npx expo install react-native-svg
# bare React Native, iOS
npx pod-install

The React components in glyphfox/react are not for React Native: they render DOM <svg> elements, and React Native has no host component with that name. Use the core engine described below.

How it works

renderToString(icon, options) from glyphfox/core takes icon data and the shared props and returns a complete SVG string. Everything that changes geometry happens in JavaScript before the string exists: the weight axis with its optical compensation, round or sharp corners, the duotone and solid styles, rotation and flips, modifier badges with their knockout gap, and fusions. react-native-svg then parses the string once and draws native shapes.

What does not survive is anything that relies on CSS in a browser: the living state animations, the CSS only morph and the right to left mirroring class. The section on limits below lists each one with its native replacement.

A small Glyph component

Wrap SvgXml once and use it everywhere. The string is memoized, so re-rendering the parent does not parse the SVG again, and the color is passed to SvgXml as a prop so a theme change does not rebuild the string.

Glyph.tsx
import { useMemo } from 'react';
import { SvgXml } from 'react-native-svg';
import { renderToString } from 'glyphfox/core';
import type { IconData, SvgOptions } from 'glyphfox';

type Props = Omit<SvgOptions, 'size' | 'color'> & { icon: IconData; size?: number; color?: string };

export function Glyph({ icon, size = 24, color = '#121014', ...options }: Props) {
  const key = JSON.stringify(options);
  const xml = useMemo(() => renderToString(icon, { ...options, size }), [icon, size, key]);
  return <SvgXml xml={xml} width={size} height={size} color={color} />;
}
Toolbar.tsx
import { View } from 'react-native';
import home from 'glyphfox/icons/home';
import bell from 'glyphfox/icons/bell';
import folder from 'glyphfox/icons/folder';
import { Glyph } from './Glyph';

export function Toolbar() {
  return (
    <View style={{ flexDirection: 'row', gap: 16 }}>
      <Glyph icon={home} size={22} color="#ff7a1a" />
      <Glyph icon={bell} modifier="dot" weight={1.5} />
      <Glyph icon={folder} variant="duotone" />
    </View>
  );
}

The strokes use currentColor, and react-native-svg resolves it from the color prop. Follow the system theme with useColorScheme() and pass a different color; the cached SVG string stays the same.

Keep the bundle small

Metro does not tree shake. Whatever a module imports ends up in the app bundle, so the way you import icons decides how much ships:

ImportWhat shipsUse it for
glyphfox/core plus glyphfox/icons/NAMEThe engine and the icons you import, a few hundred bytes per iconMost apps
toSvg from glyphfoxThe engine and every icon in the setPrototypes, or icons chosen by name at runtime
A subset package built with the CLIOnly finished SVG strings, no engine at allA fixed set of icons with fixed props

The CLI turns a list of icons into a small package whose exports are plain SVG strings, with the props baked in at build time. Add the folder to your app as a workspace or file: dependency and pass the strings to SvgXml. Dashes in names become underscores, so arrow-right is exported as arrow_right:

terminal
npx glyphfox build home search bell arrow-right --type package --name @acme/icons --weight 1.75 --out packages/icons
Header.tsx
import { SvgXml } from 'react-native-svg';
import { home, arrow_right } from '@acme/icons';

export function Header() {
  return (
    <>
      <SvgXml xml={home} width={24} height={24} color="#121014" />
      <SvgXml xml={arrow_right} width={20} height={20} color="#121014" />
    </>
  );
}

Metro and package exports

glyphfox/core and glyphfox/icons/NAME are subpath exports declared in the exports field of the package. Metro reads that field by default from React Native 0.79. On older versions, turn it on in the Metro config, otherwise these imports fail with an unable to resolve module error:

metro.config.js
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');

module.exports = mergeConfig(getDefaultConfig(__dirname), {
  resolver: { unstable_enablePackageExports: true },
});

Always hand icon data, not names, to the engine. The web packages load unknown names with a dynamic import of the icon folder, which works in web bundlers but is not something Metro follows; with data in hand, nothing ever needs to be loaded.

Styles, modifiers and fusions

The composition tools of the core work on data too. fuse combines 2 to 10 icons into a new icon that still follows the icon rules, and every option below is applied when the string is built:

Sync.tsx
import { fuse } from 'glyphfox/core';
import cloud from 'glyphfox/icons/cloud';
import lock from 'glyphfox/icons/lock';
import heart from 'glyphfox/icons/heart';
import { Glyph } from './Glyph';

const cloudLock = fuse([cloud, lock], { layout: 'overlap' });

export function Sync({ synced }: { synced: boolean }) {
  return (
    <>
      <Glyph icon={cloudLock} size={28} />
      <Glyph icon={cloud} modifier={synced ? 'check' : 'sync'} modifierCorner="top-right" />
      <Glyph icon={heart} variant="solid" corners="sharp" />
    </>
  );
}

Create fusions at module level or memoize them: fuse computes the knockout geometry, which is cheap once but wasteful on every render.

Animating a morph

There is no CSS to transition, but the morph engine itself is plain JavaScript. createMorph(from, to) prepares a morph once, using the hand tuned pairs such as menu to x and play to pause automatically, and frame(t) returns icon data for any progress from 0 to 1. tween from the core drives the progress with requestAnimationFrame:

MenuButtonIcon.tsx
import { useEffect, useRef, useState } from 'react';
import { AccessibilityInfo } from 'react-native';
import { SvgXml } from 'react-native-svg';
import { createMorph, renderToString, tween } from 'glyphfox/core';
import menu from 'glyphfox/icons/menu';
import x from 'glyphfox/icons/x';

const morph = createMorph(menu, x);

export function MenuButtonIcon({ open, color = '#121014' }: { open: boolean; color?: string }) {
  const [t, setT] = useState(open ? 1 : 0);
  const current = useRef(t);
  current.current = t;

  useEffect(() => {
    let alive = true;
    let cancel = () => {};
    AccessibilityInfo.isReduceMotionEnabled().then((reduce) => {
      if (alive) cancel = tween(current.current, open ? 1 : 0, { duration: reduce ? 0 : 280 }, setT);
    });
    return () => {
      alive = false;
      cancel();
    };
  }, [open]);

  return <SvgXml xml={renderToString(morph.frame(t), { size: 24 })} width={24} height={24} color={color} />;
}

Each frame builds and parses a new string, which is fine for a single icon over a few hundred milliseconds. Avoid running many morphs at once, and keep long running motion such as a spinner on a native transform of a wrapping view instead.

Accessibility

The title, role and aria attributes inside the SVG string do not reach the native accessibility tree, so set accessibility props on the component around the icon. An icon inside a labelled button needs nothing more; a standalone icon that carries meaning becomes an image with a label:

Status.tsx
import { Pressable, View } from 'react-native';
import search from 'glyphfox/icons/search';
import cloud from 'glyphfox/icons/cloud';
import { Glyph } from './Glyph';

export function Status({ onSearch }: { onSearch: () => void }) {
  return (
    <>
      <Pressable accessibilityRole="button" accessibilityLabel="Search" onPress={onSearch}>
        <Glyph icon={search} />
      </Pressable>
      <View accessible accessibilityRole="image" accessibilityLabel="All changes synced">
        <Glyph icon={cloud} modifier="check" />
      </View>
    </>
  );
}

The general rules for when an icon needs a label are in the accessibility guide.

What does not carry over

FeatureOn the webIn React Native
Living states (animate)Keyframes in a <style> element inside the SVGSvgXml ignores the style element. Animate a wrapping view with Animated or Reanimated, for example a rotation for a spinner.
CSS only morph (morphTo with a trigger)A CSS transition of the path dataRender frames with createMorph as shown above.
mirrorRtlA class that flips the icon inside dir="rtl"Pass flipX={I18nManager.isRTL} for directional icons.
title and decorativeA title element, role="img" and aria-hiddenAccessibility props on the wrapper.
Relative sizes"1em", "2rem"Numbers only.

Props

These options of renderToString and toSvg change the drawing itself and work in React Native. They have the same names and meaning as the props of every other glyphfox package.

OptionTypeDefaultDescription
sizenumber, string24Width and height; numbers are pixels.
colorstringcurrentColorMain color.
secondaryOpacitynumber0.3 / 1Opacity of the duotone layer or of the solid fill.
weightnumber2Stroke from 1 to 3 on the 24 grid, optically compensated below 24 px.
absoluteWeightbooleanfalseKeeps the stroke at weight pixels at any size.
cornersround, sharproundRound or sharp joins and caps.
variantoutline, duotone, solidoutlineStyle axis; solid comes from the closed shapes.
rotatenumber0Rotation in degrees.
flipXbooleanfalseMirror horizontally.
flipYbooleanfalseMirror vertically.
modifierplus, minus, check, x, alert, lock, clock, sync, arrow-up, arrow-down, dotnoneBadge with an automatic knockout gap.
modifierCornertop-left, top-right, bottom-left, bottom-rightbottom-rightCorner of the badge.

Pitfalls

Using glyphfox/react
The web components render DOM elements, so React Native stops with an error about a missing view config for svg. Render through SvgXml instead.
The whole set in the bundle
import { toSvg } from 'glyphfox' pulls every icon into the app, because Metro keeps all of it. Import icon data one by one, or build a subset package.
Unable to resolve glyphfox/core
Package exports are off in Metro before React Native 0.79. Enable unstable_enablePackageExports.
Parsing on every render
An inline renderToString call in a long list builds and parses a string per row and per render. Memoize the string, as the Glyph component does, or use prebuilt strings.
Unknown names
toSvg throws on a name that does not exist. Validate names that come from a server against iconNames, or map them to imported data yourself.

Questions

Does it work with Expo?

Yes. Install react-native-svg with npx expo install react-native-svg and use the same Glyph component. Expo uses Metro, so the note on package exports applies: SDKs built on React Native 0.79 or newer need no change.

How do I make a loading spinner?

Draw loader or loader-circle with the Glyph component and rotate a wrapping Animated.View with a looping timing animation. The native driver keeps it smooth, and the SVG string is built only once.

Icons in this guide