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
| Prop | Type | Notes |
|---|---|---|
name / icon | string / IconDefinition | One of the two. name needs a registered icon (or the catalog root). |
size | number | string | Rendered box; the viewBox is 24 units. |
weight | number | Stroke 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. |
effect | preset key or { name, options } | One of the 13 public presets. null renders the settled icon. |
value | number 0–1 | Drives 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(). |
isActive | boolean | Gates looping presets (pulse, breathe, …). |
alt | string | Makes 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 oftoWebStyleis that path length, and the draw presets turn it intostroke-dasharrayandstroke-dashoffset. Do not write.map(toWebStyle):mappasses the array index there, which type-checks and breaks every draw. - Wrap the layers in a
<g>inside the<svg>and applyrootCssto it.bounceandrotatemove onlyiconTransform, so without that<g>they do nothing.rootCssis in viewBox units, which is why it goes on the<g>and not on the<svg>box. - A layer's
transformOriginis in viewBox units too. When it is the keywordcenter, also settransform-box: fill-boxon 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.