Malevich

Primitives/

Icon

Pluggable icon adapter. getIcon(name) → SVG string.

Icon

Pluggable icon system. Per the answered design question recorded with this component: function-based adapter — getIcon(name, size) returns an SVG string; the runtime injects it into placeholder elements at init() time.

The chrome ships in @malevich/components. Icon packs ship as separate packages (@malevich/icons-default for Phosphor, @malevich/icons-lucide, etc. — packaged as a follow-up mission; the registry API is ready and the placeholder renders correctly without any pack registered).

When to use

For animated icons that morph between states (copy ↔ check), use @malevich/morph-icons instead.

Sizes

Variant Class Default
inline .icon 1em — scales with surrounding text
Small .icon.-s size-control-s
Medium .icon.-m size-control-m
Large .icon.-l size-control-l
Extra large .icon.-xl size-control-xl

Tones

Variant Class Color
inherit .icon currentColor (inherits)
Accent .icon.-accent color.accent
Danger .icon.-danger color.danger
Success .icon.-success color.success
Warning .icon.-warning color.warning
Info .icon.-info color.info
Muted .icon.-muted color.ink-subtle

Anatomy

<!-- Inline with text (size = 1em) -->
<p>Saved <i class="icon" data-icon="check"></i></p>

<!-- With explicit size + tone -->
<i class="icon -m -success" data-icon="check"></i>

<!-- Using a non-default pack -->
<i class="icon -l" data-icon="rocket" data-icon-pack="lucide"></i>

The runtime (initIcon, auto-registered for .icon[data-icon]):

  1. Reads data-icon-pack (defaults to "default").
  2. Looks up the registered getIcon for that pack.
  3. Calls getIcon(name) and injects the returned SVG string.
  4. Adds aria-hidden="true" and focusable="false" to the SVG so the glyph is decorative by default.

Without a registered pack, the host stays empty but its sized box is reserved — no layout shift when the pack registers later (call refreshIcons() after registration).

Registering a pack

import { registerIconPack, refreshIcons, init } from "@malevich/components";
import { getIcon } from "@malevich/icons-default";

registerIconPack(getIcon);   // default pack
init();
refreshIcons();              // re-render placeholders that were waiting

A pack is just a function:

type GetIcon = (name: string, size?: number | string) => string | null;

Returns an SVG markup string for known names, null for unknown.

Multiple packs:

registerIconPack(getPhosphorIcon, "default");
registerIconPack(getLucideIcon, "lucide");
registerIconPack(getTablerIcon, "tabler");

The host element picks via data-icon-pack.

Accessibility

Edge cases

Do

Don't