Frameworks

React icons

Every glyphfox icon is a React component that renders inline SVG. The same props work on every icon, nothing needs a provider, and the components you do not import never reach your bundle.

Install

The React components live in the main glyphfox package under the glyphfox/react entry point. React is an optional peer dependency: the package works with React 16.8 and newer because it only needs hooks and forwardRef, and its test suite runs against React 19.

terminal
npm install glyphfox

There is no stylesheet to import and no build plugin to add. The package ships ESM and CommonJS builds with type declarations, so Vite, webpack, Rspack, esbuild and Parcel pick it up as is.

Render your first icons

Each icon is exported under its name in PascalCase with an Icon suffix: home becomes HomeIcon, arrow-right becomes ArrowRightIcon and message-circle becomes MessageCircleIcon. Search the icon browser to find a name; every icon page, such as arrow right, also shows the import line.

Toolbar.jsx
import { HomeIcon, SearchIcon, BellIcon } from 'glyphfox/react';

export function Toolbar() {
  return (
    <nav className="toolbar">
      <a href="/"><HomeIcon /> Home</a>
      <button type="button" aria-label="Search">
        <SearchIcon />
      </button>
      <BellIcon size={20} />
    </nav>
  );
}

Without props an icon is 24 by 24 pixels, draws with stroke="currentColor" so it takes the text color of its parent, and is marked aria-hidden="true" because most icons sit next to a visible label. Give it a title when the icon is the only thing that carries the meaning; the accessibility guide covers the patterns.

Size, color, weight and style

Every icon renders along three axes: weight, corners and style. They are plain props, so they can follow state, themes or breakpoints like any other value.

Axes.jsx
import { FolderIcon, SearchIcon, ArrowRightIcon } from 'glyphfox/react';

export function Axes() {
  return (
    <>
      <SearchIcon size={32} color="#ff7a1a" />
      <SearchIcon weight={1} />
      <SearchIcon weight={3} corners="sharp" />
      <SearchIcon size={16} weight={1.5} absoluteWeight />
      <FolderIcon variant="duotone" secondaryColor="#ff7a1a" />
      <FolderIcon variant="solid" />
      <ArrowRightIcon rotate={90} />
      <ArrowRightIcon mirrorRtl />
    </>
  );
}
  • size takes a number of pixels or any CSS length string such as "1.25em".
  • weight runs from 1 to 3 on the 24 grid. Below 24 pixels the stroke is compensated optically, up to 30 percent heavier at the smallest sizes, so a 16 pixel icon keeps the same presence as a 24 pixel one. absoluteWeight turns that off and keeps the stroke at exactly weight pixels.
  • variant="duotone" uses the hand drawn second layer when the icon has one, or a translucent fill of its closed shapes. variant="solid" fills the closed shapes and cuts the inner lines out as negative space.
  • mirrorRtl flips directional icons when they render inside dir="rtl", so the same markup works in both directions.

Icons by name

When the icon comes from data, a CMS field, a route table or a user setting, render it with Icon and a name. The name prop takes the full name syntax shared by every glyphfox package, so modifiers, fusions and styles work from a single string.

MenuItem.jsx
import { Icon } from 'glyphfox/react';

export function MenuItem({ item }) {
  // item.icon could be "settings", "file+check@tr", "cloud&lock:overlap" or "heart/solid"
  return (
    <li>
      <Icon name={item.icon} size={18} />
      {item.label}
    </li>
  );
}

Names that were not imported anywhere are loaded on demand with a dynamic import. webpack and esbuild split it into one small chunk per icon, and until that chunk arrives Icon renders an empty SVG of the right size, so the layout does not jump. A Vite production build leaves that import untouched inside node_modules, so there the name stays empty (the tree shaking guide has the details). In every bundler the fix is the same: register the data of the names you render from strings once at startup, and they render synchronously, on the first paint and on the server:

icons.js
import { registerIcons } from 'glyphfox/react';
import { iconSettings, iconFile, iconCloud, iconLock } from 'glyphfox';

registerIcons([iconSettings, iconFile, iconCloud, iconLock]);

The data exports (iconSettings, iconArrowRight...) come from the main glyphfox entry and are tree shaken like the components: importing one costs a few hundred bytes. Importing a component such as SettingsIcon registers its data as well.

Modifiers and fusions

A modifier is a small badge in a corner of any icon: plus, minus, check, x, alert, lock, clock, sync, arrow-up, arrow-down or dot. The icon underneath is cut around the badge with a knockout gap, so lines never touch at any weight.

Status.jsx
import { FileIcon, CloudIcon, Fusion } from 'glyphfox/react';

export function Status({ saved, syncing }) {
  return (
    <>
      <FileIcon modifier={saved ? 'check' : undefined} />
      <CloudIcon modifier={syncing ? 'sync' : undefined} modifierCorner="top-right" />
      <Fusion icons={['cloud', 'lock']} layout="overlap" gap={1} />
      <Fusion icons={['shield', 'heart']} layout="inside" />
    </>
  );
}

Fusion combines 2 to 10 icons into one new icon that still follows the icon rules: one weight, the 24 grid and the padding box. The layouts are overlap, side, stack, grid, orbit, badge and inside; gap sets the knockout gap in grid units, and scale and offsets fine tune each part. Fusion accepts every shared prop, so a fused icon can be duotone, animated or labelled like any other.

Morphing and living states

Three tools cover motion. MorphIcon morphs whenever its name changes. morphTo on any icon morphs toward a target when its trigger fires. animate plays a preset (draw, pop, wiggle, spin, pulse, bounce or shake) on mount, hover, click or while active is true.

Motion.jsx
import { MorphIcon, MenuIcon, BellIcon, HeartIcon } from 'glyphfox/react';

export function Motion({ open, copied, hasNews, liked }) {
  return (
    <>
      <MorphIcon name={copied ? 'check' : 'copy'} duration={300} />
      <MenuIcon morphTo={open ? 'x' : undefined} />
      <BellIcon animate="shake" trigger="state" active={hasNews} />
      <HeartIcon animate="pop" trigger="click" variant={liked ? 'solid' : 'outline'} />
    </>
  );
}

For a custom renderer, useMorph(name, { duration }) returns the icon data of the current frame, which you can pass to Icon through its icon prop or to your own drawing code. Every animation respects prefers-reduced-motion. The animated icons guide goes through presets, triggers and the hand tuned morph pairs.

CustomMorph.jsx
import { Icon, useMorph } from 'glyphfox/react';

export function PlayButton({ playing }) {
  const frame = useMorph(playing ? 'pause' : 'play', { duration: 250 });
  return frame ? <Icon icon={frame.icon} variant={frame.variant} size={28} /> : null;
}

Refs, className, style and events

The components are forwardRef components, so a ref points at the rendered <svg> element. className is added after the built in glyphfox glyphfox-NAME classes, style is passed as is, and every other SVG attribute or event handler (onClick, data-*, aria-*, id) goes straight to the element.

Like.jsx
import { useRef } from 'react';
import { HeartIcon } from 'glyphfox/react';

export function Like({ onLike }) {
  const ref = useRef(null);
  const like = () => {
    ref.current?.animate([{ transform: 'scale(1)' }, { transform: 'scale(1.25)' }, { transform: 'scale(1)' }], 240);
    onLike();
  };
  return (
    <button type="button" onClick={like}>
      <HeartIcon ref={ref} className="like-icon" style={{ color: 'var(--accent)' }} />
      Like
    </button>
  );
}

An aria-label passed this way also marks the icon as meaningful: the component switches from aria-hidden to role="img" automatically.

TypeScript

Types ship with the package. GlyphIconComponent is the type of every icon component, GlyphProps are its props (the shared props plus every SVG attribute), and IconName from the main entry is the union of every icon name, which gives you autocompletion on Icon and in your own data.

nav.tsx
import type { IconName } from 'glyphfox';
import { Icon, HomeIcon, SettingsIcon, type GlyphIconComponent } from 'glyphfox/react';

type NavItem = { label: string; href: string; icon: GlyphIconComponent };
const items: NavItem[] = [
  { label: 'Home', href: '/', icon: HomeIcon },
  { label: 'Settings', href: '/settings', icon: SettingsIcon },
];

export function Nav() {
  return (
    <nav>
      {items.map(({ label, href, icon: ItemIcon }) => (
        <a key={href} href={href}>
          <ItemIcon size={18} /> {label}
        </a>
      ))}
    </nav>
  );
}

export function Badge({ name }: { name: IconName }) {
  return <Icon name={name} variant="duotone" />;
}

Imports and bundle size

Named imports from glyphfox/react are tree shaken: each icon is its own module, created with a pure annotation, and the package declares which files have side effects. Deep imports such as glyphfox/react/icons/home load a single icon module directly, which helps tools that do not tree shake and keeps development servers from touching the full index.

deep-imports.js
import { HomeIcon } from 'glyphfox/react/icons/home';
import FileIcon from 'glyphfox/react/icons/file'; // default export works too

What breaks tree shaking, what the shared runtime costs and why the build output contains one tiny chunk per icon is measured in the tree shaking guide.

Props

Every icon component, Icon, Fusion and MorphIcon take the same props. They have the same names and meaning in Vue, Svelte, Angular, the web component and toSvg, so a value copied from the studio works everywhere.

PropTypeDefaultDescription
sizenumber, string24Width and height; numbers are pixels.
colorstringcurrentColorMain color.
secondaryColorstringcolorColor of the duotone layer and of the solid fill.
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.
mirrorRtlbooleanfalseMirror in right to left layouts.
animatedraw, pop, wiggle, spin, pulse, bounce, shakenoneLiving state preset.
triggermount, hover, click, statemountWhen the animation or the morph plays.
activebooleanfalseThe state for trigger state.
durationnumber (ms)per presetAnimation or morph duration.
delaynumber (ms)0Animation or morph delay.
loopboolean, numberfalseRepeat forever or a number of times (spin and pulse loop by default).
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.
morphToname, datanoneMorph target; the trigger decides when it shows.
titlestringnoneAccessible name: title, role img and aria-label.
decorativebooleantrue without a titleHides the icon from assistive technology.

Icon and MorphIcon also take name (any name syntax) or icon (icon data). Fusion takes icons, layout, gap, scale and offsets.

Pitfalls

Icon renders empty at first
A name that is not registered yet is fetched with a dynamic import, so the first render (and the server render) is an empty SVG. Import the component, or call registerIcons with the data of every name you render by string.
Next.js Server Components
The components use hooks and the package has no "use client" directive, so a Server Component cannot render them directly. The Next.js guide shows the one file fix and a zero JavaScript option.
Coloring with fill
The icons are strokes on fill="none". Passing fill fills every shape, which is rarely what you want. Use color (or CSS color on a parent) for the stroke and variant="solid" for filled shapes.
Global CSS overrides the weight
A stylesheet rule such as svg { stroke-width: 1.5 } wins over the SVG attribute that carries the weight axis, including its optical compensation. Scope such rules or use the weight prop.
Relative sizes skip optical compensation
Compensation needs to know the pixel size. With size="1em" the stroke stays at the plain weight, which can look thin at small text sizes; pass a pixel number where it matters, or a slightly higher weight.
Calling createIcon during render
createIcon(data) turns your own icon data into a component with every prop. Call it once at module level: calling it inside a component creates a new component type on every render and remounts the SVG.
import * with a dynamic key
Icons[name] on a namespace import keeps every icon in the bundle. Use Icon with a name instead, which loads only what is rendered.

Questions

Can I use my own icons with the same props?

Yes. createIcon(data) from glyphfox/react takes icon data (name, body, tags, categories) and returns a component with every shared prop, including weight, the duotone and solid styles, modifiers and morphing. Draw the icon in the studio editor or convert an existing SVG with Fox-ify so it follows the same grid and stroke rules.

How do I set a default size or weight for every icon?

The React package has no provider, so the usual way is a small wrapper such as const NavIcon = (props) => <Icon size={20} weight={1.75} {...props} />. Color needs nothing: icons follow the CSS color of their parent.

Icons in this guide