aweft

@aweftjs/icons

Icon sets as modules. Name an icon in your source and the build puts that one icon in your page; name one when the page runs and a resolver fetches it. This package holds no drawings of its own and never will.

npm install @iconify-json/lucide

The sets are optional peers, so nothing is installed until you pick one. Any published set works: @iconify-json/lucide, @iconify-json/mdi, @iconify-json/tabler, @iconify-json/svg-spinners.

Naming an icon in your source

import { Icon } from '@aweftjs/ui';

<Icon name="lucide:check" label="done" />

@aweftjs/build rewrites that literal into an import of that one icon, a few hundred bytes (over Lucide: median 325, largest 978), and @aweftjs/icons generates the module it imports. Nothing else in the set reaches your page.

The rewrite fires for a string literal shaped set:name, on the name prop of the Icon your file bound from @aweftjs/ui, written as JSX or as an h(Icon, { ... }) call. Nothing else moves: a name with no colon in it, a value that is not a string literal, a spread that could carry its own name, and an Icon bound from anywhere else all come out as they went in and are looked up through Icons when the page runs.

You can write the import yourself, and it is the same module:

import check from '@aweftjs/icons/lucide/check';

<Icon name={check} />

Three subpaths exist, and all three are generated from the set you installed:

importwhat you get
@aweftjs/icons/lucide/checkone icon, as IconData
@aweftjs/icons/lucide/+standardthe names ui's components ask for, as an IconPack
@aweftjs/icons/lucidethe whole set as an IconPack, about 588 KB of it

The + in +standard is deliberate: no set can publish a name with one in it, so that path can never be an icon you wanted.

They are not files, so they need aweft() in your bundler or the Node loader:

import { aweft } from '@aweftjs/build';
export default { plugins: [aweft()] };

and node --import @aweftjs/build/loader where you render a page without a browser. That is the same plugin and the same loader that compile your JSX; there is nothing extra to register.

A set named node could not be reached this way, because @aweftjs/icons/node is the generator's own subpath. No published set has that name.

The names your components ask for

Everything in @aweftjs/ui that shows an icon asks for a name, never a drawing: chevron-down, chevron-up, chevron-left, chevron-right, check, x, triangle-alert, search, upload. The list is standardIcons, exported by ui.

import { Icons } from '@aweftjs/ui';
import standard from '@aweftjs/icons/lucide/+standard';

<Icons value={standard}><App /></Icons>

That costs about ten icons rather than a set. To give one of those names a drawing of your own, put a pack in front:

<Icons value={[{ icons: { 'chevron-left': myOwnChevron } }, standard]}><App /></Icons>

The nearest source wins, so your pack answers and nothing else changes.

A spinner that moves

@aweftjs/ui ships no drawings and its default loader is three pulsing spans. For a drawn one, svg-spinners is a set like any other and its icons carry their own motion:

npm install @iconify-json/svg-spinners
import { Icon, LoaderContext } from '@aweftjs/ui';
import spinner from '@aweftjs/icons/svg-spinners/3-dots-fade';

<LoaderContext value={{ loading: () => <Icon name={spinner} size="1.25em" /> }}><App /></LoaderContext>

That one line covers every wait in ui: a suspend, a Button running a promise, and a FileDrop entry being uploaded.

Nothing special happens for an animated set. Icon writes an icon's body into the element as markup, so the <animate> elements inside these drawings are ordinary SVG animation the moment the page has them, on a fresh mount and on markup a server wrote. That is checked in ui's own suite, "an animated body is written through as it is".

A name you only have when the page runs

import { fromUrl } from '@aweftjs/icons';

<Icons value={[standard, fromUrl('https://api.iconify.design')]}><App /></Icons>

fromUrl(base) fetches <base>/<set>.json?icons=<name> and reads the answer the public icon APIs give. Point it at a route of your own to serve the icons yourself; recipes/icons/main.ts has that route in about fifteen lines, built out of an installed set.

Nothing installs it. A page that never adds it makes no requests, because a fetch to somebody else's service is a decision you make and not one a library makes for you.

It answers null for a name with no set in it, so check keeps being answered locally. A page whose server fetched an icon has to hand the client what the server got, as a pack: a hydration waits for nothing, so a resolver alone leaves the client one drawing short of the markup it is taking over.

A drawing that arrives this way is somebody else's markup, and Icon writes it into the page. So a body that can run or reach out is refused with unsafe-body, naming the icon: one carrying <script, an event attribute such as onload=, <foreignObject, a javascript: URL, or an href that does not begin with #. A <use href="#id"> and a fill="url(#id)" pass. The refusal is thrown, not answered as null, so the page hears which source refused and why rather than falling through to the next. An installed set is read at build time and is not read for this.

When something is missing

A set you have not installed:

set-not-installed: the icon set "tabler" is not installed; run: npm install @iconify-json/tabler.
Install the icon set the message names, or hand a pack of your own to Icons.

A name the set does not have is icon-not-in-set, naming both. Both happen while the page is being built, not while it is running, which is the point of naming an icon in your source.

A name nothing in your Icons stack answers is a loud assert from ui in development, and nothing at all in a release build. The full list of refusals is errors.txt.

What this package never decides

Which sets you install. Whether your page fetches anything. What an icon looks like. It ships no icon data, and nothing in the gate reads this package for drawings. npm run dependencies at the repo root checks something else, that no icon set is a dependency rather than an optional peer.

API

Every export of @aweftjs/icons, its signature as the compiler resolves it, and its block comment.

@aweftjs/icons

IconSet

interface IconSet { readonly prefix?: string; readonly icons?: Readonly<Record<string, SetIcon>>; readonly aliases?: Readonly<Record<string, SetAlias>>; readonly width?: number; readonly height?: number; }

A whole set, or the part of one an API answered with.

SetAlias

interface SetAlias { readonly parent: string; readonly rotate?: number; readonly hFlip?: boolean; readonly vFlip?: boolean; }

One name pointing at another in the same set, with its own turns and flips on top.

SetIcon

interface SetIcon { readonly body: string; readonly width?: number; readonly height?: number; readonly left?: number; readonly top?: number; readonly rotate?: number; readonly hFlip?: boolean; readonly vFlip?: boolean; }

One icon as a set publishes it: the drawing, and its box only where it differs from the set's.

fromUrl

fromUrl: (base: string) => (name: string) => Promise<IconData | null>

A resolver that fetches one icon at a time from an icon API.

Nothing installs this. Add it to Icons where you want it, and a page that does not stays a page that makes no requests.

The request is <base>/<set>.json?icons=<name> and the answer is { prefix, icons: { <name>: data }, aliases?, width?, height? }. A root size in the answer applies to an icon that carries none, and an alias is followed once.

Params

  • base: where the API lives, with no trailing slash, such as https://api.iconify.design

Returns a resolver Icons takes. It answers null for a name with no set in it, for a set that does not know the name, and for an answer that is not the shape above, whether it fails to parse at all or parses to something else, so the next source in the stack is asked. A request that fails to reach the far end rejects, and Icon reports that naming the icon and the reason.

Throws unsafe-body when the drawing carries a <script, an event attribute, a <foreignObject, a javascript: URL, or an href that does not begin with #. The next source is not asked: the page hears which source refused and why.

Example

<Icons value={[myPack, fromUrl('https://api.iconify.design')]}><App /></Icons>

@aweftjs/icons/node

moduleFor

moduleFor: (request: string, from: string) => string | null

The source of the module one @aweftjs/icons import names.

Three requests are answered, and they are the three subpaths an application writes:

requestthe module
lucidethe whole installed set as an IconPack, with its root size
lucide/checkone icon as IconData, an alias followed once, the set's box applied
lucide/+standardthe names in ui's standardIcons that this set has, as an IconPack

A name the set lacks is left out of the standard selection rather than refused, because the application answers it with a pack of its own in front.

Params

  • request: what follows @aweftjs/icons/ in the import

  • from: the directory to resolve @iconify-json/<set> from, usually the directory of the file that wrote the import

Returns the module's source, export default { ... }, or null when the request is none of the three, which leaves the import to ordinary module resolution. A set name and an icon name are the sets' own grammar, lowercase letters and digits with single dashes between them, and +standard is the one exception; anything else is not a request.

Throws a refusal with reason set-not-installed when @iconify-json/<set> does not resolve from from, whose message carries the install command; icon-not-in-set when the set has no icon under the name asked for.

Example

const source = moduleFor('lucide/check', dirname(importer));

Refusals

Every error this package raises carries a reason to switch on and a fix that says what to do. These are its reasons, from errors.txt.

reasonfix
icon-not-in-setName an icon the set publishes, or install the set that has it.
set-not-installedInstall the icon set the message names, or hand a pack of your own to Icons.
unsafe-bodyServe icons from a source you trust, or take that icon out of the set it came from.

Recipes

The programs in the stack's gate that use this package, each a job someone would have.

  • recipes/icons: Icons named three ways, and what each way puts in the bundle