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.