Open Symbolsnpm

Getting started

Use an icon today (no install)

Select any icon in the library to open the inspector; /?icon=wifi01 links straight to one. The inspector's Code zone has three tabs:

  • SVG (the default) is the static, settled icon as a standalone SVG at the size and stroke weight you chose. Strokes use currentColor, so an inlined SVG takes the surrounding text colour.
  • React is the <Icon> call for the current preset, size and weight, for the packages below.
  • JSON is the resolved icon definition — layers, roles, geometry, motion hints — that the engine consumes.

The copy button copies the open tab; Download SVG and Download JSON save those two files from any tab. Static means exactly that: the SVG does not animate. The animation lives in the engine, and the engine ships in the packages below.

Packages

All seven packages are available as version 0.2.0 on npm. Choose an integration below, or see React Native and Lottie export.

React

npm install @open-symbols/react @open-symbols/core

@open-symbols/core is a peer of the React adapter so your app holds exactly one icon registry.

Register the icons you use (recommended)

The lean entry ships no icon data. Import each icon module you need — every icon is its own tree-shakable module — and either register it by name or pass it straight to <Icon icon>:

import { Icon, registerIcons } from "@open-symbols/react";
import wifi from "@open-symbols/core/icons/wifi01";
import bell from "@open-symbols/core/icons/notification01";

registerIcons([wifi, bell]); // once, at app start (server and client)

export function Status() {
  return (
    <>
      <Icon name="wifi01" effect="variableColor" value={0.66} />
      <Icon icon={bell} effect="bounce" alt="New notification" />
    </>
  );
}

Use the whole catalog

The package root is the lean entry (no icon data). @open-symbols/react/catalog resolves every icon by string name and re-exports the catalog accessors (getIcon, listIconMeta, …); it ships the whole corpus (hundreds of KB gzipped), so use it for icon browsers and editors, not app chrome.

import { Icon } from "@open-symbols/react/catalog";
<Icon name="arrow-reload-horizontal" effect="rotate" size={32} weight={1.5} />

Props you will use

PropTypeNotes
name / iconstring / IconDefinitionOne of the two. name needs a registered icon (or the catalog root).
sizenumber | stringRendered box; the viewBox is 24 units.
weightnumberStroke width in viewBox units. Omitted, it follows the size curve (DEFAULT_WEIGHT_CURVE): 1.25 at 16 px, 1.5 at 24 px, 2.0 at 48 px. Pass a number to fix it. Rendered stroke is weight × size ÷ 24 CSS px. Numbers only in the public API.
effectpreset key or { name, options }One of the 13 public presets. null renders the settled icon.
valuenumber 0–1Drives variableColor and progress on icons that support a value.
trigger"auto" | "hover" | "tap" | "manual"auto plays on mount and whenever effect changes; manual waits for the imperative handle's trigger().
isActivebooleanGates looping presets (pulse, breathe, …).
altstringMakes the icon an accessible image; omitted = decorative.
reducedMotion"system" | "always" | "never"See Reduced motion.
renderingMode"monochrome"The only supported mode at 0.x: every layer paints with currentColor.
transition—Mount/unmount transitions including replace are experimental at 0.x.

Framework-free (core engine)

The engine has no DOM dependency. You can compute frames anywhere JavaScript runs — Node, workers, build steps — and paint them yourself with toWebStyle or your own renderer:

import { createEngine } from "@open-symbols/core/engine";
import { DEFAULT_CONFIG } from "@open-symbols/core/config";
import { toRootWebStyle, toWebStyle } from "@open-symbols/core/platform-web";
import wifi from "@open-symbols/core/icons/wifi01";

const engine = createEngine();
const frame = engine.compute(wifi, "drawOn", DEFAULT_CONFIG, { elapsedMs: 250, reducedMotion: false });
// frame.instructions: one RenderInstruction per layer, keyed by layerId
// frame.iconTransform: the whole-symbol pose (bounce, rotate, some wiggles)
// frame.settled: true once the frame is at rest
const layerCss = frame.instructions.map((instruction) => toWebStyle(instruction, 100));
const rootCss = toRootWebStyle(frame.iconTransform); // null when the symbol is at rest

Paint it the way the adapters do:

  • Give every layer element pathLength="100". The second argument of toWebStyle is that path length, and the draw presets turn it into stroke-dasharray and stroke-dashoffset. Do not write .map(toWebStyle): map passes the array index there, which type-checks and breaks every draw.
  • Wrap the layers in a <g> inside the <svg> and apply rootCss to it. bounce and rotate move only iconTransform, so without that <g> they do nothing. rootCss is in viewBox units, which is why it goes on the <g> and not on the <svg> box.
  • A layer's transformOrigin is in viewBox units too. When it is the keyword center, also set transform-box: fill-box on that layer so it pivots on its own box.

Framework-free (DOM adapter and Web Component)

npm install @open-symbols/dom @open-symbols/core
import { mountIcon } from "@open-symbols/dom";
import wifi from "@open-symbols/core/icons/wifi01";

const handle = mountIcon(document.getElementById("status")!, wifi, { effect: "pulse" });
// handle.pause(); handle.resume(); handle.destroy();

Create a <span id="status"></span> in your page before mounting. For a custom element instead:

npm install @open-symbols/wc @open-symbols/core
import { registerIcons } from "@open-symbols/core/registry";
import wifi from "@open-symbols/core/icons/wifi01";
import "@open-symbols/wc/define";

registerIcons([wifi]);
<open-symbol name="wifi01" effect="pulse" size="32"></open-symbol>

For resolved icon data without a renderer, install @open-symbols/icons. It provides per-icon JSON, an index, meanings and JSON Schema; the package carries no JavaScript runtime.

Server rendering

<Icon> renders its settled frame on the server (verified by rendering the lean Icon with react-dom/server: every layer is emitted at opacity: 1), so there is no faint first frame in the HTML. The animation kicks off after mount; prefers-reduced-motion is read through matchMedia once the component is on the client. The barrel @open-symbols/react carries no "use client" directive because it also re-exports registry accessors that server components read; the component modules declare it themselves, so importing Icon from a server component is safe.

In Next.js App Router, put registerIcons in a client module that also exports the component using those names. Registering only in a server component does not populate the client's registry. Passing a per-icon definition through <Icon icon={wifi} /> avoids name registration.