Reference

Core Exports

TypeScript
// One named export per icon, 2,146 of them
import { IconShoppingCart, IconArrowRight } from '@devigner-ui/icons';

// Types
import type {
  IconName,
  IconStyle,
  IconVariant,
  IconProps,
  IconComponentType,
} from '@devigner-ui/icons';

// Data, as plain JSON
import metadata from '@devigner-ui/icons/metadata.json';
import embeddings from '@devigner-ui/icons/embeddings.json';

// Optional stylesheet
import '@devigner-ui/icons/styles.css';

Those are the only entry points. There is no default export, no Icon shell component, and no /ai subpath.

Icon components

Every icon is a ref-forwarding component that renders its own <svg>. The ref lands on that <svg>.

Props

TypeScript
type IconVariant = 'Outline' | 'TwoTone' | 'Bold' | 'Bulk';

interface IconProps extends SVGProps<SVGSVGElement> {
  /** Sizes and colours the glyph. Strokes are currentColor. */
  className?: string;
  /** Which of the four drawings to render. */
  variant?: IconVariant;
  /** Stroke width, in the 24px drawing's own units. */
  strokeWidth?: string | number;
}

Three props of its own, on top of everything an <svg> accepts: onClick, style, id, width, fill, data-* and the aria attributes all pass through to the element, and the ref reaches it too.

There is still no size prop; size comes from className. Nothing is omitted from the inherited set: width and fill used to be taken over for stroke width and which drawing, and are the element's own attributes again.

Accessibility

Icons render aria-hidden="true" and focusable="false" by default, because an icon beside its own text label would otherwise be announced twice. Pass aria-label or aria-labelledby and the icon becomes role="img" and is no longer hidden.

TSX
<IconTrash className="size-4" />                 {/* decorative, hidden */}
<IconTrash aria-label="Delete" className="size-4" />   {/* role="img" */}

Styles

A style is one variant string:

Style variant What it draws
outline "Outline" (the default) Full linework, every stroke at 1.5
twotone "TwoTone" The same linework, secondary strokes at half tone
bold "Bold" No strokes. One filled form, cut with even-odd holes
bulk "Bulk" Filled, with the secondary mass at half tone
TSX
<IconShoppingCart className="size-6" />                         {/* outline */}
<IconShoppingCart variant="TwoTone" className="size-6" />
<IconShoppingCart variant="Bold" className="size-6" />
<IconShoppingCart variant="Bulk" className="size-6" />
<IconShoppingCart strokeWidth={2} className="size-6 text-blue-600" />

The defaults are variant = "Outline" and strokeWidth = "1.5", so a bare <IconShoppingCart /> is outline at 1.5.

strokeWidth only reaches the linework, which Bold and Bulk do not have.

icons copy <name> --style=<style> prints the right prop for you.

Types

IconName

A literal union of all 2,146 icon names, with the Icon prefix left off:

TypeScript
type IconName = '24Support' | '3dCubeScan' | 'ShoppingCart' | /* ... */;

const name: IconName = 'ShoppingCart'; // ✅
const invalid: IconName = 'NotExist';  // ❌ Type error

IconStyle

The lowercase spelling the metadata, the CLI and the URLs use.

TypeScript
type IconStyle = 'outline' | 'twotone' | 'bold' | 'bulk';

IconVariant

The same four drawings, spelled the way the variant prop takes them.

TypeScript
type IconVariant = 'Outline' | 'TwoTone' | 'Bold' | 'Bulk';

IconComponentType

TypeScript
type IconComponentType = ForwardRefExoticComponent<
  IconProps & RefAttributes<SVGSVGElement>
>;

Not FC: every icon forwards its ref to the <svg>, which is what Radix, Headless UI and any tooltip or popover trigger need in order to measure and position against it.

The components carry no static metadata: no displayName, keywords, category or styles properties. That information is in metadata.json, keyed by name.

There is deliberately no IconMap alias, because one of the icons is called Map and the export IconMap is already that component. Write it out:

TypeScript
const byName: Partial<Record<IconName, IconComponentType>> = { ShoppingCart: IconShoppingCart };

IconMetadata

The shape of each entry in metadata.json. It is data, not an exported type:

TypeScript
interface IconMetadata {
  name: string;
  displayName: string;
  category: string;
  keywords: string[];
  pathCount: number;
  styles: Record<IconStyle, 'free' | 'pro'>;
  freeStyles: IconStyle[];
  proStyles: IconStyle[];
  usage: string;
}

Metadata

metadata.json is a JSON file, so it is a default import; there is no named metadata export to destructure.

TypeScript
import metadata from '@devigner-ui/icons/metadata.json';

metadata.version;      // 1
metadata.generatedAt;  // ISO timestamp
metadata.totalIcons;   // 2146
metadata.icons;        // IconMetadata[]
TypeScript
const icon = metadata.icons.find(i => i.name === 'ShoppingCart');

icon.freeStyles;  // ['outline', 'twotone', 'bold', 'bulk']
icon.proStyles;   // []

const arrowIcons = metadata.icons.filter(i => i.category === 'arrows-direction');

proStyles is empty for every icon: all 8,584 icons are free. The field is kept so that consumers read the data rather than assume, and there is a single place to change if anything is ever sold again.

Embeddings

embeddings.json is Record<string, number[]>: one 384-dimension vector per icon name, quantized to int8. The vectors were produced by Xenova/all-MiniLM-L6-v2 from each icon's name, category, keywords and usage.

There is no similarity helper to import; the arithmetic is three lines and inlining it keeps you from depending on a subpath that does not exist.

TypeScript
import { pipeline } from '@xenova/transformers';
import metadata from '@devigner-ui/icons/metadata.json';
import embeddings from '@devigner-ui/icons/embeddings.json';

async function searchIcons(query: string, limit = 10) {
  // Must be the model that produced the vectors. bge-small is also 384
  // dimensions, so a mismatch throws nothing and simply scores in the wrong
  // space.
  const extract = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2', {
    quantized: true,
  });
  const out = await extract(query, { pooling: 'mean', normalize: true });
  const q = Array.from(out.data as Float32Array);

  return metadata.icons
    .map(icon => {
      const vec = embeddings[icon.name] ?? [];
      let dot = 0;
      for (let i = 0; i < q.length; i++) dot += q[i] * (vec[i] ?? 0);
      // The stored vectors are unit length scaled by 127, and the query is
      // unit length, so this is cosine * 127.
      return { icon, score: dot / 127 };
    })
    .filter(r => r.score > 0.15)
    .sort((a, b) => b.score - a.score)
    .slice(0, limit);
}

@xenova/transformers is not a dependency of this package. Installing icons does not pull a machine-learning runtime into your project; you add it only if you want to embed queries yourself. The icons search command does exactly the above; see the CLI reference.

CLI

The package ships one executable, icons. Full options are in the CLI reference.

Terminal
npx @devigner-ui/icons <command> [options]

Commands

Command Description
search <query> Rank every icon against a description
list List icons, with filters
info <name> Show one icon's metadata
categories List categories and their counts
copy <name> Print an icon as a React snippet or as SVG

Examples

Terminal
# Semantic search over the shipped vectors
npx @devigner-ui/icons search "shopping cart in header" --limit=5

# Print one icon's snippet
npx @devigner-ui/icons copy ShoppingCart --style=outline

# Write a family out as SVG
npx @devigner-ui/icons copy "Arrow*" --format=svg --out=./arrows

# List with filters
npx @devigner-ui/icons list --category=arrows-direction --style=outline

Ranking a query needs @xenova/transformers. Without it, search falls back to keyword matching and says so.

CSS Variables

The stylesheet is optional. It gives you one class and three variables; the components work without it.

CSS
@import '@devigner-ui/icons/styles.css';

:root {
  --icon-size: 24px;
  --icon-stroke-width: 2;
  --icon-color: currentColor;
}
TSX
<IconShoppingCart className="icon" />

.icon reads all three, and stroke-width in CSS wins over the attribute the component renders, so it overrides the strokeWidth prop.

Rendering a style by name

There is nothing to check before use: every style of every icon is free. Map the style to its props:

TSX
import type { IconStyle, IconVariant, IconComponentType } from '@devigner-ui/icons';

const VARIANT: Record<IconStyle, IconVariant> = {
  outline: 'Outline',
  twotone: 'TwoTone',
  bold: 'Bold',
  bulk: 'Bulk',
};

function StyledIcon({
  Component,
  style,
}: {
  Component: IconComponentType;
  style: IconStyle;
}) {
  return <Component variant={VARIANT[style]} className="size-6" />;
}

Documentation

Browse all 8,584 icons