Icons
There's no @zoijs/icons, because icons in a no-build framework are just SVG — static markup the platform already renders. Pick one of three patterns by how many icons you have and how you want to manage them. None needs reactivity, state, or a dependency.
Why no package? A package would only wrap static SVG. Nothing here is reactive or stateful, so there's no runtime problem to solve. See Decision 0007.
1. Inline SVG (simplest)#
Drop the SVG straight into a template. It's styleable with currentColor, and dynamic attributes work like any other binding:
import { html } from "@zoijs/core";
const Check = () => html`
<svg class="icon" viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"
fill="none" stroke="currentColor" stroke-width="2">
<path d="M20 6 9 17l-5-5" />
</svg>
`;
html`<button>${Check()} Save</button>`;stroke="currentColor" makes the icon inherit the surrounding text color, so it themes itself. SVG inside html works as long as it's rooted at <svg> — see Bindings.
2. A sprite sheet with <use> (best for many icons)#
Define every icon once in a hidden sprite, then reference them by id. The markup per use is tiny, and the browser caches the sprite:
<!-- once, e.g. in index.html or injected at startup -->
<svg style=class="tok-string">"display:none">
<symbol id=class="tok-string">"i-check" viewBox=class="tok-string">"0 0 24 24"><path d=class="tok-string">"M20 6 9 17l-5-5"/></symbol>
<symbol id=class="tok-string">"i-trash" viewBox=class="tok-string">"0 0 24 24"><path d=class="tok-string">"M3 6h18M8 6V4h8v2m-9 0v14h10V6"/></symbol>
</svg>import { html } from "@zoijs/core";
const Icon = (name) => html`
<svg class="icon" width="20" height="20" aria-hidden="true">
<use href=${`#i-${name}`} />
</svg>
`;
html`<button>${Icon("trash")} Delete</button>`;A reactive name works too: <use href=${() => #i-${current.get()}} /> swaps the icon when state changes — one attribute update, no re-render.
3. An icon component with a registry#
For a typed-ish set with sizing and a11y baked in, make a small component over a plain object of path data — no library, just a map:
import { html } from "@zoijs/core";
const PATHS = {
check: "M20 6 9 17l-5-5",
trash: "M3 6h18M8 6V4h8v2m-9 0v14h10V6",
};
function Icon({ name, size = 20, label }) {
return html`
<svg viewBox="0 0 24 24" width=${size} height=${size}
role=${label ? "img" : "presentation"} aria-label=${label ?? null} aria-hidden=${label ? null : "true"}
fill="none" stroke="currentColor" stroke-width="2">
<path d=${PATHS[name]} />
</svg>
`;
}
html`${Icon({ name: "check", label: "Done" })}`;Accessibility#
- Decorative icons (next to a text label) get
aria-hidden="true"so screen readers skip them. - Standalone icons (an icon-only button) need a name:
role="img"+aria-label, or a visually-hidden<span>label on the button.
Security note#
An inline <svg> you author is just markup — safe. An <img src="icon.svg"> or a remote sprite URL is treated like any other URL: Zoijs blocks dangerous schemes on src, but don't render untrusted SVG (a third party's SVG can carry scripts). Keep icon sources first-party, or sanitize them server-side. See Security.
That's the cookbook. Back to the overview.